commit 9d3c32538e054fbbc8f9963195124972480c89af
parent de6de8f177b0034015d492473267cc4245cf23af
Author: Dan Callaghan <djc@djc.id.au>
Date: Sat, 11 Dec 2010 12:39:08 +1000
delete unused third-party code
Diffstat:
125 files changed, 0 insertions(+), 40957 deletions(-)
diff --git a/lib/Paste.egg-info/PKG-INFO b/lib/Paste.egg-info/PKG-INFO
@@ -1,119 +0,0 @@
-Metadata-Version: 1.0
-Name: Paste
-Version: 1.7.2
-Summary: Tools for using a Web Server Gateway Interface stack
-Home-page: http://pythonpaste.org
-Author: Ian Bicking
-Author-email: ianb@colorstudy.com
-License: MIT
-Description: These provide several pieces of "middleware" (or filters) that can be nested to build web applications. Each
- piece of middleware uses the WSGI (`PEP 333`_) interface, and should
- be compatible with other middleware based on those interfaces.
-
- .. _PEP 333: http://www.python.org/peps/pep-0333.html
-
- Includes these features...
-
- Testing
- -------
-
- * A fixture for testing WSGI applications conveniently and in-process,
- in ``paste.fixture``
-
- * A fixture for testing command-line applications, also in
- ``paste.fixture``
-
- * Check components for WSGI-compliance in ``paste.lint``
-
- Dispatching
- -----------
-
- * Chain and cascade WSGI applications (returning the first non-error
- response) in ``paste.cascade``
-
- * Dispatch to several WSGI applications based on URL prefixes, in
- ``paste.urlmap``
-
- * Allow applications to make subrequests and forward requests
- internally, in ``paste.recursive``
-
- Web Application
- ---------------
-
- * Run CGI programs as WSGI applications in ``paste.cgiapp``
-
- * Traverse files and load WSGI applications from ``.py`` files (or
- static files), in ``paste.urlparser``
-
- * Serve static directories of files, also in ``paste.urlparser``; also
- in that module serving from Egg resources using ``pkg_resources``.
-
- Tools
- -----
-
- * Catch HTTP-related exceptions (e.g., ``HTTPNotFound``) and turn them
- into proper responses in ``paste.httpexceptions``
-
- * Several authentication techniques, including HTTP (Basic and
- Digest), signed cookies, and CAS single-signon, in the
- ``paste.auth`` package.
-
- * Create sessions in ``paste.session`` and ``paste.flup_session``
-
- * Gzip responses in ``paste.gzip``
-
- * A wide variety of routines for manipulating WSGI requests and
- producing responses, in ``paste.request``, ``paste.response`` and
- ``paste.wsgilib``
-
- Debugging Filters
- -----------------
-
- * Catch (optionally email) errors with extended tracebacks (using
- Zope/ZPT conventions) in ``paste.exceptions``
-
- * Catch errors presenting a `cgitb
- <http://python.org/doc/current/lib/module-cgitb.html>`_-based
- output, in ``paste.cgitb_catcher``.
-
- * Profile each request and append profiling information to the HTML,
- in ``paste.debug.profile``
-
- * Capture ``print`` output and present it in the browser for
- debugging, in ``paste.debug.prints``
-
- * Validate all HTML output from applications using the `WDG Validator
- <http://www.htmlhelp.com/tools/validator/>`_, appending any errors
- or warnings to the page, in ``paste.debug.wdg_validator``
-
- Other Tools
- -----------
-
- * A file monitor to allow restarting the server when files have been
- updated (for automatic restarting when editing code) in
- ``paste.reloader``
-
- * A class for generating and traversing URLs, and creating associated
- HTML code, in ``paste.url``
-
- The latest version is available in a `Subversion repository
- <http://svn.pythonpaste.org/Paste/trunk#egg=Paste-dev>`_.
-
- For the latest changes see the `news file
- <http://pythonpaste.org/news.html>`_.
-
-
-Keywords: web application server wsgi
-Platform: UNKNOWN
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Programming Language :: Python
-Classifier: Topic :: Internet :: WWW/HTTP
-Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server
-Classifier: Framework :: Paste
diff --git a/lib/Paste.egg-info/SOURCES.txt b/lib/Paste.egg-info/SOURCES.txt
@@ -1,232 +0,0 @@
-regen-docs
-setup.cfg
-setup.py
-Paste.egg-info/PKG-INFO
-Paste.egg-info/SOURCES.txt
-Paste.egg-info/dependency_links.txt
-Paste.egg-info/entry_points.txt
-Paste.egg-info/namespace_packages.txt
-Paste.egg-info/not-zip-safe
-Paste.egg-info/requires.txt
-Paste.egg-info/top_level.txt
-docs/DeveloperGuidelines.txt
-docs/StyleGuide.txt
-docs/conf.py
-docs/default.css
-docs/developer-features.txt
-docs/do-it-yourself-framework.txt
-docs/future.txt
-docs/index.txt
-docs/license.txt
-docs/news.txt
-docs/paste-httpserver-threadpool.txt
-docs/test_server.ini
-docs/testing-applications.txt
-docs/url-parsing-with-wsgi.txt
-docs/_static/paste.css
-docs/_templates/layout.html
-docs/community/index.txt
-docs/community/mailing-list.txt
-docs/community/repository.txt
-docs/download/index.txt
-docs/include/contact.txt
-docs/include/reference_header.txt
-docs/modules/auth.auth_tkt.txt
-docs/modules/auth.basic.txt
-docs/modules/auth.cas.txt
-docs/modules/auth.cookie.txt
-docs/modules/auth.digest.txt
-docs/modules/auth.form.txt
-docs/modules/auth.grantip.txt
-docs/modules/auth.multi.txt
-docs/modules/auth.open_id.txt
-docs/modules/cascade.txt
-docs/modules/cgiapp.txt
-docs/modules/cgitb_catcher.txt
-docs/modules/debug.debugapp.txt
-docs/modules/debug.fsdiff.txt
-docs/modules/debug.prints.txt
-docs/modules/debug.profile.txt
-docs/modules/debug.watchthreads.txt
-docs/modules/debug.wdg_validate.txt
-docs/modules/errordocument.txt
-docs/modules/evalexception.txt
-docs/modules/exceptions.txt
-docs/modules/fileapp.txt
-docs/modules/fixture.txt
-docs/modules/gzipper.txt
-docs/modules/httpexceptions.txt
-docs/modules/httpheaders.txt
-docs/modules/httpserver.txt
-docs/modules/lint.txt
-docs/modules/pony.txt
-docs/modules/progress.txt
-docs/modules/proxy.txt
-docs/modules/recursive.txt
-docs/modules/registry.txt
-docs/modules/reloader.txt
-docs/modules/request.txt
-docs/modules/response.txt
-docs/modules/session.txt
-docs/modules/transaction.txt
-docs/modules/translogger.txt
-docs/modules/url.txt
-docs/modules/urlmap.txt
-docs/modules/urlparser.txt
-docs/modules/util.import_string.txt
-docs/modules/util.multidict.txt
-docs/modules/wsgilib.txt
-docs/modules/wsgiwrappers.txt
-docs/web/default-site.css
-docs/web/site.js
-docs/web/style.css
-paste/__init__.py
-paste/cascade.py
-paste/cgiapp.py
-paste/cgitb_catcher.py
-paste/config.py
-paste/errordocument.py
-paste/fileapp.py
-paste/fixture.py
-paste/flup_session.py
-paste/gzipper.py
-paste/httpexceptions.py
-paste/httpheaders.py
-paste/httpserver.py
-paste/lint.py
-paste/modpython.py
-paste/pony.py
-paste/progress.py
-paste/proxy.py
-paste/recursive.py
-paste/registry.py
-paste/reloader.py
-paste/request.py
-paste/response.py
-paste/session.py
-paste/transaction.py
-paste/translogger.py
-paste/url.py
-paste/urlmap.py
-paste/urlparser.py
-paste/wsgilib.py
-paste/wsgiwrappers.py
-paste/auth/__init__.py
-paste/auth/auth_tkt.py
-paste/auth/basic.py
-paste/auth/cas.py
-paste/auth/cookie.py
-paste/auth/digest.py
-paste/auth/form.py
-paste/auth/grantip.py
-paste/auth/multi.py
-paste/auth/open_id.py
-paste/debug/__init__.py
-paste/debug/debugapp.py
-paste/debug/doctest_webapp.py
-paste/debug/fsdiff.py
-paste/debug/prints.py
-paste/debug/profile.py
-paste/debug/testserver.py
-paste/debug/watchthreads.py
-paste/debug/wdg_validate.py
-paste/evalexception/__init__.py
-paste/evalexception/evalcontext.py
-paste/evalexception/middleware.py
-paste/evalexception/media/debug.js
-paste/evalexception/media/minus.jpg
-paste/evalexception/media/plus.jpg
-paste/evalexception/mochikit/MochiKit.js
-paste/evalexception/mochikit/__package__.js
-paste/exceptions/__init__.py
-paste/exceptions/collector.py
-paste/exceptions/errormiddleware.py
-paste/exceptions/formatter.py
-paste/exceptions/reporter.py
-paste/exceptions/serial_number_generator.py
-paste/util/PySourceColor.py
-paste/util/UserDict24.py
-paste/util/__init__.py
-paste/util/classinit.py
-paste/util/classinstance.py
-paste/util/converters.py
-paste/util/dateinterval.py
-paste/util/datetimeutil.py
-paste/util/doctest24.py
-paste/util/filemixin.py
-paste/util/finddata.py
-paste/util/findpackage.py
-paste/util/import_string.py
-paste/util/intset.py
-paste/util/ip4.py
-paste/util/killthread.py
-paste/util/looper.py
-paste/util/mimeparse.py
-paste/util/multidict.py
-paste/util/quoting.py
-paste/util/scgiserver.py
-paste/util/string24.py
-paste/util/subprocess24.py
-paste/util/template.py
-paste/util/threadedprint.py
-paste/util/threadinglocal.py
-tests/conftest.py
-tests/test_cgiapp.py
-tests/test_cgitb_catcher.py
-tests/test_config.py
-tests/test_doctests.py
-tests/test_errordocument.py
-tests/test_fileapp.py
-tests/test_fixture.py
-tests/test_grantip.py
-tests/test_gzipper.py
-tests/test_httpheaders.py
-tests/test_import_string.py
-tests/test_multidict.py
-tests/test_profilemiddleware.py
-tests/test_proxy.py
-tests/test_recursive.py
-tests/test_registry.py
-tests/test_request.py
-tests/test_request_form.py
-tests/test_response.py
-tests/test_session.py
-tests/test_template.txt
-tests/test_urlmap.py
-tests/test_urlparser.py
-tests/test_wsgiwrappers.py
-tests/cgiapp_data/error.cgi
-tests/cgiapp_data/form.cgi
-tests/cgiapp_data/ok.cgi
-tests/cgiapp_data/stderr.cgi
-tests/test_auth/test_auth_cookie.py
-tests/test_auth/test_auth_digest.py
-tests/test_exceptions/__init__.py
-tests/test_exceptions/test_error_middleware.py
-tests/test_exceptions/test_formatter.py
-tests/test_exceptions/test_httpexceptions.py
-tests/test_exceptions/test_reporter.py
-tests/test_util/test_datetimeutil.py
-tests/urlparser_data/__init__.py
-tests/urlparser_data/secured.txt
-tests/urlparser_data/deep/index.html
-tests/urlparser_data/deep/sub/Main.txt
-tests/urlparser_data/find_file/index.txt
-tests/urlparser_data/find_file/test 3.html
-tests/urlparser_data/find_file/test2.html
-tests/urlparser_data/find_file/dir with spaces/test 4.html
-tests/urlparser_data/hook/__init__.py
-tests/urlparser_data/hook/app.py
-tests/urlparser_data/hook/index.py
-tests/urlparser_data/not_found/__init__.py
-tests/urlparser_data/not_found/recur/__init__.py
-tests/urlparser_data/not_found/recur/isfound.txt
-tests/urlparser_data/not_found/simple/__init__.py
-tests/urlparser_data/not_found/simple/found.txt
-tests/urlparser_data/not_found/user/__init__.py
-tests/urlparser_data/not_found/user/list.py
-tests/urlparser_data/python/__init__.py
-tests/urlparser_data/python/simpleapp.py
-tests/urlparser_data/python/stream.py
-tests/urlparser_data/python/sub/__init__.py
-tests/urlparser_data/python/sub/simpleapp.py
-\ No newline at end of file
diff --git a/lib/Paste.egg-info/dependency_links.txt b/lib/Paste.egg-info/dependency_links.txt
@@ -1 +0,0 @@
-
diff --git a/lib/Paste.egg-info/entry_points.txt b/lib/Paste.egg-info/entry_points.txt
@@ -1,46 +0,0 @@
-
- [paste.app_factory]
- cgi = paste.cgiapp:make_cgi_application [subprocess]
- static = paste.urlparser:make_static
- pkg_resources = paste.urlparser:make_pkg_resources
- urlparser = paste.urlparser:make_url_parser
- proxy = paste.proxy:make_proxy
- test = paste.debug.debugapp:make_test_app
- test_slow = paste.debug.debugapp:make_slow_app
- transparent_proxy = paste.proxy:make_transparent_proxy
- watch_threads = paste.debug.watchthreads:make_watch_threads
-
- [paste.composite_factory]
- urlmap = paste.urlmap:urlmap_factory
- cascade = paste.cascade:make_cascade
-
- [paste.filter_app_factory]
- error_catcher = paste.exceptions.errormiddleware:make_error_middleware
- cgitb = paste.cgitb_catcher:make_cgitb_middleware
- flup_session = paste.flup_session:make_session_middleware [Flup]
- gzip = paste.gzipper:make_gzip_middleware
- httpexceptions = paste.httpexceptions:make_middleware
- lint = paste.lint:make_middleware
- printdebug = paste.debug.prints:PrintDebugMiddleware
- profile = paste.debug.profile:make_profile_middleware [hotshot]
- recursive = paste.recursive:make_recursive_middleware
- # This isn't good enough to deserve the name egg:Paste#session:
- paste_session = paste.session:make_session_middleware
- wdg_validate = paste.debug.wdg_validate:make_wdg_validate_middleware [subprocess]
- evalerror = paste.evalexception.middleware:make_eval_exception
- auth_tkt = paste.auth.auth_tkt:make_auth_tkt_middleware
- auth_basic = paste.auth.basic:make_basic
- auth_digest = paste.auth.digest:make_digest
- auth_form = paste.auth.form:make_form
- grantip = paste.auth.grantip:make_grantip
- openid = paste.auth.open_id:make_open_id_middleware [openid]
- pony = paste.pony:make_pony
- errordocument = paste.errordocument:make_errordocument
- auth_cookie = paste.auth.cookie:make_auth_cookie
- translogger = paste.translogger:make_filter
- config = paste.config:make_config_filter
- registry = paste.registry:make_registry_manager
-
- [paste.server_runner]
- http = paste.httpserver:server_runner
-
-\ No newline at end of file
diff --git a/lib/Paste.egg-info/namespace_packages.txt b/lib/Paste.egg-info/namespace_packages.txt
@@ -1 +0,0 @@
-paste
diff --git a/lib/Paste.egg-info/not-zip-safe b/lib/Paste.egg-info/not-zip-safe
@@ -1 +0,0 @@
-
diff --git a/lib/Paste.egg-info/requires.txt b/lib/Paste.egg-info/requires.txt
@@ -1,15 +0,0 @@
-
-
-[Flup]
-flup
-
-[openid]
-python-openid
-
-[Paste]
-
-
-[hotshot]
-
-
-[subprocess]
diff --git a/lib/Paste.egg-info/top_level.txt b/lib/Paste.egg-info/top_level.txt
@@ -1 +0,0 @@
-paste
diff --git a/lib/WebOb.egg-info/PKG-INFO b/lib/WebOb.egg-info/PKG-INFO
@@ -1,23 +0,0 @@
-Metadata-Version: 1.0
-Name: WebOb
-Version: 0.9.4
-Summary: WSGI request and response object
-Home-page: http://pythonpaste.org/webob/
-Author: Ian Bicking
-Author-email: ianb@colorstudy.com
-License: MIT
-Description: WebOb provides wrappers around the WSGI request environment, and an
- object to help create WSGI responses.
-
- The objects map much of the specified behavior of HTTP, including
- header parsing and accessors for other standard parts of the
- environment.
-
-Keywords: wsgi request web http
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: Framework :: Paste
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
diff --git a/lib/WebOb.egg-info/SOURCES.txt b/lib/WebOb.egg-info/SOURCES.txt
@@ -1,50 +0,0 @@
-regen-docs
-setup.cfg
-setup.py
-test
-WebOb.egg-info/PKG-INFO
-WebOb.egg-info/SOURCES.txt
-WebOb.egg-info/dependency_links.txt
-WebOb.egg-info/top_level.txt
-WebOb.egg-info/zip-safe
-docs/comment-example.txt
-docs/conf.py
-docs/differences.txt
-docs/do-it-yourself.txt
-docs/file-example.txt
-docs/index.txt
-docs/jsonrpc-example.txt
-docs/license.txt
-docs/news.txt
-docs/reference.txt
-docs/test-file.txt
-docs/wiki-example.txt
-docs/comment-example-code/example.py
-docs/jsonrpc-example-code/jsonrpc.py
-docs/jsonrpc-example-code/test_jsonrpc.py
-docs/jsonrpc-example-code/test_jsonrpc.txt
-docs/modules/webob.txt
-docs/wiki-example-code/example.py
-tests/__init__.py
-tests/conftest.py
-tests/test_request.py
-tests/test_request.txt
-tests/test_response.py
-tests/test_response.txt
-webob/__init__.py
-webob/acceptparse.py
-webob/byterange.py
-webob/cachecontrol.py
-webob/compat.py
-webob/datastruct.py
-webob/etag.py
-webob/exc.py
-webob/headerdict.py
-webob/multidict.py
-webob/statusreasons.py
-webob/updatedict.py
-webob/util/__init__.py
-webob/util/dictmixin.py
-webob/util/reversed.py
-webob/util/safegzip.py
-webob/util/stringtemplate.py
-\ No newline at end of file
diff --git a/lib/WebOb.egg-info/dependency_links.txt b/lib/WebOb.egg-info/dependency_links.txt
@@ -1 +0,0 @@
-
diff --git a/lib/WebOb.egg-info/top_level.txt b/lib/WebOb.egg-info/top_level.txt
@@ -1 +0,0 @@
-webob
diff --git a/lib/WebOb.egg-info/zip-safe b/lib/WebOb.egg-info/zip-safe
@@ -1 +0,0 @@
-
diff --git a/lib/paste/__init__.py b/lib/paste/__init__.py
@@ -1,17 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-try:
- import pkg_resources
- pkg_resources.declare_namespace(__name__)
-except ImportError:
- # don't prevent use of paste if pkg_resources isn't installed
- from pkgutil import extend_path
- __path__ = extend_path(__path__, __name__)
-
-try:
- import modulefinder
-except ImportError:
- pass
-else:
- for p in __path__:
- modulefinder.AddPackagePath(__name__, p)
diff --git a/lib/paste/auth/__init__.py b/lib/paste/auth/__init__.py
@@ -1,9 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Package for authentication/identification of requests.
-
-The objective of this package is to provide single-focused middleware
-components that implement a particular specification. Integration of
-the components into a usable system is up to a higher-level framework.
-"""
diff --git a/lib/paste/auth/auth_tkt.py b/lib/paste/auth/auth_tkt.py
@@ -1,352 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-##########################################################################
-#
-# Copyright (c) 2005 Imaginary Landscape LLC and Contributors.
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-##########################################################################
-"""
-Implementation of cookie signing as done in `mod_auth_tkt
-<http://www.openfusion.com.au/labs/mod_auth_tkt/>`_.
-
-mod_auth_tkt is an Apache module that looks for these signed cookies
-and sets ``REMOTE_USER``, ``REMOTE_USER_TOKENS`` (a comma-separated
-list of groups) and ``REMOTE_USER_DATA`` (arbitrary string data).
-
-This module is an alternative to the ``paste.auth.cookie`` module;
-it's primary benefit is compatibility with mod_auth_tkt, which in turn
-makes it possible to use the same authentication process with
-non-Python code run under Apache.
-"""
-
-import time as time_mod
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
-import Cookie
-from paste import request
-
-class AuthTicket(object):
-
- """
- This class represents an authentication token. You must pass in
- the shared secret, the userid, and the IP address. Optionally you
- can include tokens (a list of strings, representing role names),
- 'user_data', which is arbitrary data available for your own use in
- later scripts. Lastly, you can override the cookie name and
- timestamp.
-
- Once you provide all the arguments, use .cookie_value() to
- generate the appropriate authentication ticket. .cookie()
- generates a Cookie object, the str() of which is the complete
- cookie header to be sent.
-
- CGI usage::
-
- token = auth_tkt.AuthTick('sharedsecret', 'username',
- os.environ['REMOTE_ADDR'], tokens=['admin'])
- print 'Status: 200 OK'
- print 'Content-type: text/html'
- print token.cookie()
- print
- ... redirect HTML ...
-
- Webware usage::
-
- token = auth_tkt.AuthTick('sharedsecret', 'username',
- self.request().environ()['REMOTE_ADDR'], tokens=['admin'])
- self.response().setCookie('auth_tkt', token.cookie_value())
-
- Be careful not to do an HTTP redirect after login; use meta
- refresh or Javascript -- some browsers have bugs where cookies
- aren't saved when set on a redirect.
- """
-
- def __init__(self, secret, userid, ip, tokens=(), user_data='',
- time=None, cookie_name='auth_tkt',
- secure=False):
- self.secret = secret
- self.userid = userid
- self.ip = ip
- self.tokens = ','.join(tokens)
- self.user_data = user_data
- if time is None:
- self.time = time_mod.time()
- else:
- self.time = time
- self.cookie_name = cookie_name
- self.secure = secure
-
- def digest(self):
- return calculate_digest(
- self.ip, self.time, self.secret, self.userid, self.tokens,
- self.user_data)
-
- def cookie_value(self):
- v = '%s%08x%s!' % (self.digest(), int(self.time), self.userid)
- if self.tokens:
- v += self.tokens + '!'
- v += self.user_data
- return v
-
- def cookie(self):
- c = Cookie.SimpleCookie()
- c[self.cookie_name] = self.cookie_value().encode('base64').strip().replace('\n', '')
- c[self.cookie_name]['path'] = '/'
- if self.secure:
- c[self.cookie_name]['secure'] = 'true'
- return c
-
-class BadTicket(Exception):
- """
- Exception raised when a ticket can't be parsed. If we get
- far enough to determine what the expected digest should have
- been, expected is set. This should not be shown by default,
- but can be useful for debugging.
- """
- def __init__(self, msg, expected=None):
- self.expected = expected
- Exception.__init__(self, msg)
-
-def parse_ticket(secret, ticket, ip):
- """
- Parse the ticket, returning (timestamp, userid, tokens, user_data).
-
- If the ticket cannot be parsed, ``BadTicket`` will be raised with
- an explanation.
- """
- ticket = ticket.strip('"')
- digest = ticket[:32]
- try:
- timestamp = int(ticket[32:40], 16)
- except ValueError, e:
- raise BadTicket('Timestamp is not a hex integer: %s' % e)
- try:
- userid, data = ticket[40:].split('!', 1)
- except ValueError:
- raise BadTicket('userid is not followed by !')
- if '!' in data:
- tokens, user_data = data.split('!', 1)
- else:
- # @@: Is this the right order?
- tokens = ''
- user_data = data
-
- expected = calculate_digest(ip, timestamp, secret,
- userid, tokens, user_data)
-
- if expected != digest:
- raise BadTicket('Digest signature is not correct',
- expected=(expected, digest))
-
- tokens = tokens.split(',')
-
- return (timestamp, userid, tokens, user_data)
-
-def calculate_digest(ip, timestamp, secret, userid, tokens, user_data):
- secret = maybe_encode(secret)
- userid = maybe_encode(userid)
- tokens = maybe_encode(tokens)
- user_data = maybe_encode(user_data)
- digest0 = md5(
- encode_ip_timestamp(ip, timestamp) + secret + userid + '\0'
- + tokens + '\0' + user_data).hexdigest()
- digest = md5(digest0 + secret).hexdigest()
- return digest
-
-def encode_ip_timestamp(ip, timestamp):
- ip_chars = ''.join(map(chr, map(int, ip.split('.'))))
- t = int(timestamp)
- ts = ((t & 0xff000000) >> 24,
- (t & 0xff0000) >> 16,
- (t & 0xff00) >> 8,
- t & 0xff)
- ts_chars = ''.join(map(chr, ts))
- return ip_chars + ts_chars
-
-def maybe_encode(s, encoding='utf8'):
- if isinstance(s, unicode):
- s = s.encode(encoding)
- return s
-
-class AuthTKTMiddleware(object):
-
- """
- Middleware that checks for signed cookies that match what
- `mod_auth_tkt <http://www.openfusion.com.au/labs/mod_auth_tkt/>`_
- looks for (if you have mod_auth_tkt installed, you don't need this
- middleware, since Apache will set the environmental variables for
- you).
-
- Arguments:
-
- ``secret``:
- A secret that should be shared by any instances of this application.
- If this app is served from more than one machine, they should all
- have the same secret.
-
- ``cookie_name``:
- The name of the cookie to read and write from. Default ``auth_tkt``.
-
- ``secure``:
- If the cookie should be set as 'secure' (only sent over SSL) and if
- the login must be over SSL.
-
- ``include_ip``:
- If the cookie should include the user's IP address. If so, then
- if they change IPs their cookie will be invalid.
-
- ``logout_path``:
- The path under this middleware that should signify a logout. The
- page will be shown as usual, but the user will also be logged out
- when they visit this page.
-
- If used with mod_auth_tkt, then these settings (except logout_path) should
- match the analogous Apache configuration settings.
-
- This also adds two functions to the request:
-
- ``environ['paste.auth_tkt.set_user'](userid, tokens='', user_data='')``
-
- This sets a cookie that logs the user in. ``tokens`` is a
- string (comma-separated groups) or a list of strings.
- ``user_data`` is a string for your own use.
-
- ``environ['paste.auth_tkt.logout_user']()``
-
- Logs out the user.
- """
-
- def __init__(self, app, secret, cookie_name='auth_tkt', secure=False,
- include_ip=True, logout_path=None):
- self.app = app
- self.secret = secret
- self.cookie_name = cookie_name
- self.secure = secure
- self.include_ip = include_ip
- self.logout_path = logout_path
-
- def __call__(self, environ, start_response):
- cookies = request.get_cookies(environ)
- if cookies.has_key(self.cookie_name):
- cookie_value = cookies[self.cookie_name].value
- else:
- cookie_value = ''
- if cookie_value:
- if self.include_ip:
- remote_addr = environ['REMOTE_ADDR']
- else:
- # mod_auth_tkt uses this dummy value when IP is not
- # checked:
- remote_addr = '0.0.0.0'
- # @@: This should handle bad signatures better:
- # Also, timeouts should cause cookie refresh
- timestamp, userid, tokens, user_data = parse_ticket(
- self.secret, cookie_value, remote_addr)
- tokens = ','.join(tokens)
- environ['REMOTE_USER'] = userid
- if environ.get('REMOTE_USER_TOKENS'):
- # We want to add tokens/roles to what's there:
- tokens = environ['REMOTE_USER_TOKENS'] + ',' + tokens
- environ['REMOTE_USER_TOKENS'] = tokens
- environ['REMOTE_USER_DATA'] = user_data
- environ['AUTH_TYPE'] = 'cookie'
- set_cookies = []
- def set_user(userid, tokens='', user_data=''):
- set_cookies.extend(self.set_user_cookie(
- environ, userid, tokens, user_data))
- def logout_user():
- set_cookies.extend(self.logout_user_cookie(environ))
- environ['paste.auth_tkt.set_user'] = set_user
- environ['paste.auth_tkt.logout_user'] = logout_user
- if self.logout_path and environ.get('PATH_INFO') == self.logout_path:
- logout_user()
- def cookie_setting_start_response(status, headers, exc_info=None):
- headers.extend(set_cookies)
- return start_response(status, headers, exc_info)
- return self.app(environ, cookie_setting_start_response)
-
- def set_user_cookie(self, environ, userid, tokens, user_data):
- if not isinstance(tokens, basestring):
- tokens = ','.join(tokens)
- if self.include_ip:
- remote_addr = environ['REMOTE_ADDR']
- else:
- remote_addr = '0.0.0.0'
- ticket = AuthTicket(
- self.secret,
- userid,
- remote_addr,
- tokens=tokens,
- user_data=user_data,
- cookie_name=self.cookie_name,
- secure=self.secure)
- # @@: Should we set REMOTE_USER etc in the current
- # environment right now as well?
- cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME'))
- wild_domain = '.' + cur_domain
- cookies = [
- ('Set-Cookie', '%s=%s; Path=/' % (
- self.cookie_name, ticket.cookie_value())),
- ('Set-Cookie', '%s=%s; Path=/; Domain=%s' % (
- self.cookie_name, ticket.cookie_value(), cur_domain)),
- ('Set-Cookie', '%s=%s; Path=/; Domain=%s' % (
- self.cookie_name, ticket.cookie_value(), wild_domain))
- ]
- return cookies
-
- def logout_user_cookie(self, environ):
- cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME'))
- wild_domain = '.' + cur_domain
- cookies = [
- ('Set-Cookie', '%s=""; Path=/' % self.cookie_name),
- ('Set-Cookie', '%s=""; Path=/; Domain=%s' %
- (self.cookie_name, cur_domain)),
- ('Set-Cookie', '%s=""; Path=/; Domain=%s' %
- (self.cookie_name, wild_domain)),
- ]
- return cookies
-
-def make_auth_tkt_middleware(
- app,
- global_conf,
- secret=None,
- cookie_name='auth_tkt',
- secure=False,
- include_ip=True,
- logout_path=None):
- """
- Creates the `AuthTKTMiddleware
- <class-paste.auth.auth_tkt.AuthTKTMiddleware.html>`_.
-
- ``secret`` is requird, but can be set globally or locally.
- """
- from paste.deploy.converters import asbool
- secure = asbool(secure)
- include_ip = asbool(include_ip)
- if secret is None:
- secret = global_conf.get('secret')
- if not secret:
- raise ValueError(
- "You must provide a 'secret' (in global or local configuration)")
- return AuthTKTMiddleware(
- app, secret, cookie_name, secure, include_ip, logout_path or None)
diff --git a/lib/paste/auth/basic.py b/lib/paste/auth/basic.py
@@ -1,122 +0,0 @@
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-Basic HTTP/1.0 Authentication
-
-This module implements ``Basic`` authentication as described in
-HTTP/1.0 specification [1]_ . Do not use this module unless you
-are using SSL or need to work with very out-dated clients, instead
-use ``digest`` authentication.
-
->>> from paste.wsgilib import dump_environ
->>> from paste.httpserver import serve
->>> # from paste.auth.basic import AuthBasicHandler
->>> realm = 'Test Realm'
->>> def authfunc(environ, username, password):
-... return username == password
->>> serve(AuthBasicHandler(dump_environ, realm, authfunc))
-serving on...
-
-.. [1] http://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html#BasicAA
-"""
-from paste.httpexceptions import HTTPUnauthorized
-from paste.httpheaders import *
-
-class AuthBasicAuthenticator(object):
- """
- implements ``Basic`` authentication details
- """
- type = 'basic'
- def __init__(self, realm, authfunc):
- self.realm = realm
- self.authfunc = authfunc
-
- def build_authentication(self):
- head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
- return HTTPUnauthorized(headers=head)
-
- def authenticate(self, environ):
- authorization = AUTHORIZATION(environ)
- if not authorization:
- return self.build_authentication()
- (authmeth, auth) = authorization.split(' ', 1)
- if 'basic' != authmeth.lower():
- return self.build_authentication()
- auth = auth.strip().decode('base64')
- username, password = auth.split(':', 1)
- if self.authfunc(environ, username, password):
- return username
- return self.build_authentication()
-
- __call__ = authenticate
-
-class AuthBasicHandler(object):
- """
- HTTP/1.0 ``Basic`` authentication middleware
-
- Parameters:
-
- ``application``
-
- The application object is called only upon successful
- authentication, and can assume ``environ['REMOTE_USER']``
- is set. If the ``REMOTE_USER`` is already set, this
- middleware is simply pass-through.
-
- ``realm``
-
- This is a identifier for the authority that is requesting
- authorization. It is shown to the user and should be unique
- within the domain it is being used.
-
- ``authfunc``
-
- This is a mandatory user-defined function which takes a
- ``environ``, ``username`` and ``password`` for its first
- three arguments. It should return ``True`` if the user is
- authenticated.
-
- """
- def __init__(self, application, realm, authfunc):
- self.application = application
- self.authenticate = AuthBasicAuthenticator(realm, authfunc)
-
- def __call__(self, environ, start_response):
- username = REMOTE_USER(environ)
- if not username:
- result = self.authenticate(environ)
- if isinstance(result, str):
- AUTH_TYPE.update(environ, 'basic')
- REMOTE_USER.update(environ, result)
- else:
- return result.wsgi_application(environ, start_response)
- return self.application(environ, start_response)
-
-middleware = AuthBasicHandler
-
-__all__ = ['AuthBasicHandler']
-
-def make_basic(app, global_conf, realm, authfunc, **kw):
- """
- Grant access via basic authentication
-
- Config looks like this::
-
- [filter:grant]
- 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
- doctest.testmod(optionflags=doctest.ELLIPSIS)
diff --git a/lib/paste/auth/cas.py b/lib/paste/auth/cas.py
@@ -1,99 +0,0 @@
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-CAS 1.0 Authentication
-
-The Central Authentication System is a straight-forward single sign-on
-mechanism developed by Yale University's ITS department. It has since
-enjoyed widespread success and is deployed at many major universities
-and some corporations.
-
- https://clearinghouse.ja-sig.org/wiki/display/CAS/Home
- http://www.yale.edu/tp/auth/usingcasatyale.html
-
-This implementation has the goal of maintaining current path arguments
-passed to the system so that it can be used as middleware at any stage
-of processing. It has the secondary goal of allowing for other
-authentication methods to be used concurrently.
-"""
-import urllib
-from paste.request import construct_url
-from paste.httpexceptions import HTTPSeeOther, HTTPForbidden
-
-class CASLoginFailure(HTTPForbidden):
- """ The exception raised if the authority returns 'no' """
-
-class CASAuthenticate(HTTPSeeOther):
- """ The exception raised to authenticate the user """
-
-def AuthCASHandler(application, authority):
- """
- middleware to implement CAS 1.0 authentication
-
- There are several possible outcomes:
-
- 0. If the REMOTE_USER environment variable is already populated;
- then this middleware is a no-op, and the request is passed along
- to the application.
-
- 1. If a query argument 'ticket' is found, then an attempt to
- validate said ticket /w the authentication service done. If the
- ticket is not validated; an 403 'Forbidden' exception is raised.
- Otherwise, the REMOTE_USER variable is set with the NetID that
- was validated and AUTH_TYPE is set to "cas".
-
- 2. Otherwise, a 303 'See Other' is returned to the client directing
- them to login using the CAS service. After logon, the service
- will send them back to this same URL, only with a 'ticket' query
- argument.
-
- Parameters:
-
- ``authority``
-
- This is a fully-qualified URL to a CAS 1.0 service. The URL
- should end with a '/' and have the 'login' and 'validate'
- sub-paths as described in the CAS 1.0 documentation.
-
- """
- assert authority.endswith("/") and authority.startswith("http")
- def cas_application(environ, start_response):
- username = environ.get('REMOTE_USER','')
- if username:
- return application(environ, start_response)
- qs = environ.get('QUERY_STRING','').split("&")
- if qs and qs[-1].startswith("ticket="):
- # assume a response from the authority
- ticket = qs.pop().split("=", 1)[1]
- environ['QUERY_STRING'] = "&".join(qs)
- service = construct_url(environ)
- args = urllib.urlencode(
- {'service': service,'ticket': ticket})
- requrl = authority + "validate?" + args
- result = urllib.urlopen(requrl).read().split("\n")
- if 'yes' == result[0]:
- environ['REMOTE_USER'] = result[1]
- environ['AUTH_TYPE'] = 'cas'
- return application(environ, start_response)
- exce = CASLoginFailure()
- else:
- service = construct_url(environ)
- args = urllib.urlencode({'service': service})
- location = authority + "login?" + args
- exce = CASAuthenticate(location)
- return exce.wsgi_application(environ, start_response)
- return cas_application
-
-middleware = AuthCASHandler
-
-__all__ = ['CASLoginFailure', 'CASAuthenticate', 'AuthCASHandler' ]
-
-if '__main__' == __name__:
- authority = "https://secure.its.yale.edu/cas/servlet/"
- from paste.wsgilib import dump_environ
- from paste.httpserver import serve
- from paste.httpexceptions import *
- serve(HTTPExceptionHandler(
- AuthCASHandler(dump_environ, authority)))
diff --git a/lib/paste/auth/cookie.py b/lib/paste/auth/cookie.py
@@ -1,395 +0,0 @@
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-Cookie "Saved" Authentication
-
-This authentication middleware saves the current REMOTE_USER,
-REMOTE_SESSION, and any other environment variables specified in a
-cookie so that it can be retrieved during the next request without
-requiring re-authentication. This uses a session cookie on the client
-side (so it goes away when the user closes their window) and does
-server-side expiration.
-
-Following is a very simple example where a form is presented asking for
-a user name (no actual checking), and dummy session identifier (perhaps
-corresponding to a database session id) is stored in the cookie.
-
-::
-
- >>> from paste.httpserver import serve
- >>> from paste.fileapp import DataApp
- >>> from paste.httpexceptions import *
- >>> from paste.auth.cookie import AuthCookieHandler
- >>> from paste.wsgilib import parse_querystring
- >>> def testapp(environ, start_response):
- ... user = dict(parse_querystring(environ)).get('user','')
- ... if user:
- ... environ['REMOTE_USER'] = user
- ... environ['REMOTE_SESSION'] = 'a-session-id'
- ... if environ.get('REMOTE_USER'):
- ... page = '<html><body>Welcome %s (%s)</body></html>'
- ... page %= (environ['REMOTE_USER'], environ['REMOTE_SESSION'])
- ... else:
- ... page = ('<html><body><form><input name="user" />'
- ... '<input type="submit" /></form></body></html>')
- ... return DataApp(page, content_type="text/html")(
- ... environ, start_response)
- >>> serve(AuthCookieHandler(testapp))
- serving on...
-
-"""
-
-import hmac, base64, random, time, warnings
-try:
- from hashlib import sha1
-except ImportError:
- # NOTE: We have to use the callable with hashlib (hashlib.sha1),
- # otherwise hmac only accepts the sha module object itself
- import sha as sha1
-from paste.request import get_cookies
-
-def make_time(value):
- return time.strftime("%Y%m%d%H%M", time.gmtime(value))
-_signature_size = len(hmac.new('x', 'x', sha1).digest())
-_header_size = _signature_size + len(make_time(time.time()))
-
-# @@: Should this be using urllib.quote?
-# build encode/decode functions to safely pack away values
-_encode = [('\\', '\\x5c'), ('"', '\\x22'),
- ('=', '\\x3d'), (';', '\\x3b')]
-_decode = [(v, k) for (k, v) in _encode]
-_decode.reverse()
-def encode(s, sublist = _encode):
- return reduce((lambda a, (b, c): a.replace(b, c)), sublist, str(s))
-decode = lambda s: encode(s, _decode)
-
-class CookieTooLarge(RuntimeError):
- def __init__(self, content, cookie):
- RuntimeError.__init__("Signed cookie exceeds maximum size of 4096")
- self.content = content
- self.cookie = cookie
-
-_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))
-
-class AuthCookieSigner(object):
- """
- save/restore ``environ`` entries via digially signed cookie
-
- This class converts content into a timed and digitally signed
- cookie, as well as having the facility to reverse this procedure.
- If the cookie, after the content is encoded and signed exceeds the
- maximum length (4096), then CookieTooLarge exception is raised.
-
- The timeout of the cookie is handled on the server side for a few
- reasons. First, if a 'Expires' directive is added to a cookie, then
- the cookie becomes persistent (lasting even after the browser window
- has closed). Second, the user's clock may be wrong (perhaps
- intentionally). The timeout is specified in minutes; and expiration
- date returned is rounded to one second.
-
- Constructor Arguments:
-
- ``secret``
-
- This is a secret key if you want to syncronize your keys so
- that the cookie will be good across a cluster of computers.
- It is recommended via the HMAC specification (RFC 2104) that
- the secret key be 64 bytes since this is the block size of
- the hashing. If you do not provide a secret key, a random
- one is generated each time you create the handler; this
- should be sufficient for most cases.
-
- ``timeout``
-
- This is the time (in minutes) from which the cookie is set
- to expire. Note that on each request a new (replacement)
- cookie is sent, hence this is effectively a session timeout
- parameter for your entire cluster. If you do not provide a
- timeout, it is set at 30 minutes.
-
- ``maxlen``
-
- This is the maximum size of the *signed* cookie; hence the
- actual content signed will be somewhat less. If the cookie
- goes over this size, a ``CookieTooLarge`` exception is
- raised so that unexpected handling of cookies on the client
- side are avoided. By default this is set at 4k (4096 bytes),
- which is the standard cookie size limit.
-
- """
- def __init__(self, secret = None, timeout = None, maxlen = None):
- self.timeout = timeout or 30
- if isinstance(timeout, basestring):
- raise ValueError(
- "Timeout must be a number (minutes), not a string (%r)"
- % timeout)
- self.maxlen = maxlen or 4096
- self.secret = secret or new_secret()
-
- def sign(self, content):
- """
- Sign the content returning a valid cookie (that does not
- need to be escaped and quoted). The expiration of this
- cookie is handled server-side in the auth() function.
- """
- cookie = base64.encodestring(
- hmac.new(self.secret, content, sha1).digest() +
- make_time(time.time() + 60*self.timeout) +
- content)[:-1]
- cookie = cookie.replace("/", "_").replace("=", "~")
- if len(cookie) > self.maxlen:
- raise CookieTooLarge(content, cookie)
- return cookie
-
- def auth(self, cookie):
- """
- Authenticate the cooke using the signature, verify that it
- has not expired; and return the cookie's content
- """
- decode = base64.decodestring(
- cookie.replace("_", "/").replace("~", "="))
- signature = decode[:_signature_size]
- expires = decode[_signature_size:_header_size]
- content = decode[_header_size:]
- if signature == hmac.new(self.secret, content, sha1).digest():
- if int(expires) > int(make_time(time.time())):
- return content
- else:
- # This is the normal case of an expired cookie; just
- # don't bother doing anything here.
- pass
- else:
- # This case can happen if the server is restarted with a
- # different secret; or if the user's IP address changed
- # due to a proxy. However, it could also be a break-in
- # attempt -- so should it be reported?
- pass
-
-class AuthCookieEnviron(list):
- """
- a list of environment keys to be saved via cookie
-
- An instance of this object, found at ``environ['paste.auth.cookie']``
- lists the `environ` keys that were restored from or will be added
- to the digially signed cookie. This object can be accessed from an
- `environ` variable by using this module's name.
- """
- def __init__(self, handler, scanlist):
- list.__init__(self, scanlist)
- self.handler = handler
- def append(self, value):
- if value in self:
- return
- list.append(self, str(value))
-
-class AuthCookieHandler(object):
- """
- the actual handler that should be put in your middleware stack
-
- This middleware uses cookies to stash-away a previously authenticated
- user (and perhaps other variables) so that re-authentication is not
- needed. This does not implement sessions; and therefore N servers
- can be syncronized to accept the same saved authentication if they
- all use the same cookie_name and secret.
-
- By default, this handler scans the `environ` for the REMOTE_USER
- and REMOTE_SESSION key; if found, it is stored. It can be
- configured to scan other `environ` keys as well -- but be careful
- not to exceed 2-3k (so that the encoded and signed cookie does not
- exceed 4k). You can ask it to handle other environment variables
- by doing:
-
- ``environ['paste.auth.cookie'].append('your.environ.variable')``
-
-
- Constructor Arguments:
-
- ``application``
-
- This is the wrapped application which will have access to
- the ``environ['REMOTE_USER']`` restored by this middleware.
-
- ``cookie_name``
-
- The name of the cookie used to store this content, by default
- it is ``PASTE_AUTH_COOKIE``.
-
- ``scanlist``
-
- This is the initial set of ``environ`` keys to
- save/restore to the signed cookie. By default is consists
- only of ``REMOTE_USER`` and ``REMOTE_SESSION``; any tuple
- or list of environment keys will work. However, be
- careful, as the total saved size is limited to around 3k.
-
- ``signer``
-
- This is the signer object used to create the actual cookie
- values, by default, it is ``AuthCookieSigner`` and is passed
- the remaining arguments to this function: ``secret``,
- ``timeout``, and ``maxlen``.
-
- At this time, each cookie is individually signed. To store more
- than the 4k of data; it is possible to sub-class this object to
- provide different ``environ_name`` and ``cookie_name``
- """
- environ_name = 'paste.auth.cookie'
- cookie_name = 'PASTE_AUTH_COOKIE'
- signer_class = AuthCookieSigner
- environ_class = AuthCookieEnviron
-
- def __init__(self, application, cookie_name=None, scanlist=None,
- signer=None, secret=None, timeout=None, maxlen=None):
- if not signer:
- signer = self.signer_class(secret, timeout, maxlen)
- self.signer = signer
- self.scanlist = scanlist or ('REMOTE_USER','REMOTE_SESSION')
- self.application = application
- self.cookie_name = cookie_name or self.cookie_name
-
- def __call__(self, environ, start_response):
- if self.environ_name in environ:
- raise AssertionError("AuthCookie already installed!")
- scanlist = self.environ_class(self, self.scanlist)
- jar = get_cookies(environ)
- if jar.has_key(self.cookie_name):
- content = self.signer.auth(jar[self.cookie_name].value)
- if content:
- for pair in content.split(";"):
- (k, v) = pair.split("=")
- k = decode(k)
- if k not in scanlist:
- scanlist.append(k)
- if k in environ:
- continue
- environ[k] = decode(v)
- if 'REMOTE_USER' == k:
- environ['AUTH_TYPE'] = 'cookie'
- environ[self.environ_name] = scanlist
- if "paste.httpexceptions" in environ:
- warnings.warn("Since paste.httpexceptions is hooked in your "
- "processing chain before paste.auth.cookie, if an "
- "HTTPRedirection is raised, the cookies this module sets "
- "will not be included in your response.\n")
-
- def response_hook(status, response_headers, exc_info=None):
- """
- Scan the environment for keys specified in the scanlist,
- pack up their values, signs the content and issues a cookie.
- """
- scanlist = environ.get(self.environ_name)
- assert scanlist and isinstance(scanlist, self.environ_class)
- content = []
- for k in scanlist:
- v = environ.get(k)
- if v is not None:
- if type(v) is not str:
- raise ValueError(
- "The value of the environmental variable %r "
- "is not a str (only str is allowed; got %r)"
- % (k, v))
- content.append("%s=%s" % (encode(k), encode(v)))
- if content:
- content = ";".join(content)
- content = self.signer.sign(content)
- cookie = '%s=%s; Path=/;' % (self.cookie_name, content)
- if 'https' == environ['wsgi.url_scheme']:
- cookie += ' secure;'
- response_headers.append(('Set-Cookie', cookie))
- return start_response(status, response_headers, exc_info)
- return self.application(environ, response_hook)
-
-middleware = AuthCookieHandler
-
-# Paste Deploy entry point:
-def make_auth_cookie(
- app, global_conf,
- # Should this get picked up from global_conf somehow?:
- cookie_name='PASTE_AUTH_COOKIE',
- scanlist=('REMOTE_USER', 'REMOTE_SESSION'),
- # signer cannot be set
- secret=None,
- timeout=30,
- maxlen=4096):
- """
- This middleware uses cookies to stash-away a previously
- authenticated user (and perhaps other variables) so that
- re-authentication is not needed. This does not implement
- sessions; and therefore N servers can be syncronized to accept the
- same saved authentication if they all use the same cookie_name and
- secret.
-
- By default, this handler scans the `environ` for the REMOTE_USER
- and REMOTE_SESSION key; if found, it is stored. It can be
- configured to scan other `environ` keys as well -- but be careful
- not to exceed 2-3k (so that the encoded and signed cookie does not
- exceed 4k). You can ask it to handle other environment variables
- by doing:
-
- ``environ['paste.auth.cookie'].append('your.environ.variable')``
-
- Configuration:
-
- ``cookie_name``
-
- The name of the cookie used to store this content, by
- default it is ``PASTE_AUTH_COOKIE``.
-
- ``scanlist``
-
- This is the initial set of ``environ`` keys to
- save/restore to the signed cookie. By default is consists
- only of ``REMOTE_USER`` and ``REMOTE_SESSION``; any
- space-separated list of environment keys will work.
- However, be careful, as the total saved size is limited to
- around 3k.
-
- ``secret``
-
- The secret that will be used to sign the cookies. If you
- don't provide one (and none is set globally) then a random
- secret will be created. Each time the server is restarted
- a new secret will then be created and all cookies will
- become invalid! This can be any string value.
-
- ``timeout``
-
- The time to keep the cookie, expressed in minutes. This
- is handled server-side, so a new cookie with a new timeout
- is added to every response.
-
- ``maxlen``
-
- The maximum length of the cookie that is sent (default 4k,
- which is a typical browser maximum)
-
- """
- if isinstance(scanlist, basestring):
- scanlist = scanlist.split()
- if secret is None and global_conf.get('secret'):
- secret = global_conf['secret']
- try:
- timeout = int(timeout)
- except ValueError:
- raise ValueError('Bad value for timeout (must be int): %r'
- % timeout)
- try:
- maxlen = int(maxlen)
- except ValueError:
- raise ValueError('Bad value for maxlen (must be int): %r'
- % maxlen)
- return AuthCookieHandler(
- app, cookie_name=cookie_name, scanlist=scanlist,
- secret=secret, timeout=timeout, maxlen=maxlen)
-
-__all__ = ['AuthCookieHandler', 'AuthCookieSigner', 'AuthCookieEnviron']
-
-if "__main__" == __name__:
- import doctest
- doctest.testmod(optionflags=doctest.ELLIPSIS)
-
diff --git a/lib/paste/auth/digest.py b/lib/paste/auth/digest.py
@@ -1,213 +0,0 @@
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-Digest HTTP/1.1 Authentication
-
-This module implements ``Digest`` authentication as described by
-RFC 2617 [1]_ .
-
-Basically, you just put this module before your application, and it
-takes care of requesting and handling authentication requests. This
-module has been tested with several common browsers "out-in-the-wild".
-
->>> from paste.wsgilib import dump_environ
->>> from paste.httpserver import serve
->>> # from paste.auth.digest import digest_password, AuthDigestHandler
->>> realm = 'Test Realm'
->>> def authfunc(environ, realm, username):
-... return digest_password(realm, username, username)
->>> serve(AuthDigestHandler(dump_environ, realm, authfunc))
-serving on...
-
-This code has not been audited by a security expert, please use with
-caution (or better yet, report security holes). At this time, this
-implementation does not provide for further challenges, nor does it
-support Authentication-Info header. It also uses md5, and an option
-to use sha would be a good thing.
-
-.. [1] http://www.faqs.org/rfcs/rfc2617.html
-"""
-from paste.httpexceptions import HTTPUnauthorized
-from paste.httpheaders import *
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
-import time, random
-
-def digest_password(realm, username, password):
- """ construct the appropriate hashcode needed for HTTP digest """
- return md5("%s:%s:%s" % (username, realm, password)).hexdigest()
-
-class AuthDigestAuthenticator(object):
- """ implementation of RFC 2617 - HTTP Digest Authentication """
- def __init__(self, realm, authfunc):
- self.nonce = {} # list to prevent replay attacks
- self.authfunc = authfunc
- self.realm = realm
-
- 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()
- self.nonce[nonce] = None
- parts = {'realm': self.realm, 'qop': 'auth',
- 'nonce': nonce, 'opaque': opaque }
- if stale:
- parts['stale'] = 'true'
- head = ", ".join(['%s="%s"' % (k, v) for (k, v) in parts.items()])
- head = [("WWW-Authenticate", 'Digest %s' % head)]
- return HTTPUnauthorized(headers=head)
-
- def compute(self, ha1, username, response, method,
- path, nonce, nc, cnonce, qop):
- """ computes the authentication, raises error if unsuccessful """
- if not ha1:
- return self.build_authentication()
- ha2 = md5('%s:%s' % (method, path)).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 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 nonce in self.nonce:
- del self.nonce[nonce]
- return self.build_authentication(stale = True)
- self.nonce[nonce] = nc
- return username
-
- def authenticate(self, environ):
- """ This function takes a WSGI environment and authenticates
- the request returning authenticated user or error.
- """
- method = REQUEST_METHOD(environ)
- fullpath = SCRIPT_NAME(environ) + PATH_INFO(environ)
- authorization = AUTHORIZATION(environ)
- if not authorization:
- return self.build_authentication()
- (authmeth, auth) = authorization.split(" ", 1)
- if 'digest' != authmeth.lower():
- return self.build_authentication()
- amap = {}
- for itm in auth.split(", "):
- (k,v) = [s.strip() for s in itm.split("=", 1)]
- amap[k] = v.replace('"', '')
- try:
- username = amap['username']
- authpath = amap['uri']
- nonce = amap['nonce']
- realm = amap['realm']
- response = amap['response']
- assert authpath.split("?", 1)[0] in fullpath
- assert realm == self.realm
- qop = amap.get('qop', '')
- cnonce = amap.get('cnonce', '')
- nc = amap.get('nc', '00000000')
- if qop:
- assert 'auth' == qop
- assert nonce and nc
- except:
- return self.build_authentication()
- ha1 = self.authfunc(environ, realm, username)
- return self.compute(ha1, username, response, method, authpath,
- nonce, nc, cnonce, qop)
-
- __call__ = authenticate
-
-class AuthDigestHandler(object):
- """
- middleware for HTTP Digest authentication (RFC 2617)
-
- This component follows the procedure below:
-
- 0. If the REMOTE_USER environment variable is already populated;
- then this middleware is a no-op, and the request is passed
- along to the application.
-
- 1. If the HTTP_AUTHORIZATION header was not provided or specifies
- an algorithem other than ``digest``, then a HTTPUnauthorized
- response is generated with the challenge.
-
- 2. If the response is malformed or or if the user's credientials
- do not pass muster, another HTTPUnauthorized is raised.
-
- 3. If all goes well, and the user's credintials pass; then
- REMOTE_USER environment variable is filled in and the
- AUTH_TYPE is listed as 'digest'.
-
- Parameters:
-
- ``application``
-
- The application object is called only upon successful
- authentication, and can assume ``environ['REMOTE_USER']``
- is set. If the ``REMOTE_USER`` is already set, this
- middleware is simply pass-through.
-
- ``realm``
-
- This is a identifier for the authority that is requesting
- authorization. It is shown to the user and should be unique
- within the domain it is being used.
-
- ``authfunc``
-
- This is a callback function which performs the actual
- authentication; the signature of this callback is:
-
- authfunc(environ, realm, username) -> hashcode
-
- This module provides a 'digest_password' helper function
- which can help construct the hashcode; it is recommended
- that the hashcode is stored in a database, not the user's
- actual password (since you only need the hashcode).
- """
- def __init__(self, application, realm, authfunc):
- self.authenticate = AuthDigestAuthenticator(realm, authfunc)
- self.application = application
-
- def __call__(self, environ, start_response):
- username = REMOTE_USER(environ)
- if not username:
- result = self.authenticate(environ)
- if isinstance(result, str):
- AUTH_TYPE.update(environ,'digest')
- REMOTE_USER.update(environ, result)
- else:
- return result.wsgi_application(environ, start_response)
- return self.application(environ, start_response)
-
-middleware = AuthDigestHandler
-
-__all__ = ['digest_password', 'AuthDigestHandler' ]
-
-def make_digest(app, global_conf, realm, authfunc, **kw):
- """
- Grant access via digest authentication
-
- Config looks like this::
-
- [filter:grant]
- use = egg:Paste#auth_digest
- 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 AuthDigestHandler(app, realm, authfunc)
-
-if "__main__" == __name__:
- import doctest
- doctest.testmod(optionflags=doctest.ELLIPSIS)
diff --git a/lib/paste/auth/form.py b/lib/paste/auth/form.py
@@ -1,149 +0,0 @@
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-Authentication via HTML Form
-
-This is a very simple HTML form login screen that asks for the username
-and password. This middleware component requires that an authorization
-function taking the name and passsword and that it be placed in your
-application stack. This class does not include any session management
-code or way to save the user's authorization; however, it is easy enough
-to put ``paste.auth.cookie`` in your application stack.
-
->>> from paste.wsgilib import dump_environ
->>> from paste.httpserver import serve
->>> from paste.auth.cookie import AuthCookieHandler
->>> from paste.auth.form import AuthFormHandler
->>> def authfunc(environ, username, password):
-... return username == password
->>> serve(AuthCookieHandler(
-... AuthFormHandler(dump_environ, authfunc)))
-serving on...
-
-"""
-from paste.request import construct_url, parse_formvars
-
-TEMPLATE = """\
-<html>
- <head><title>Please Login!</title></head>
- <body>
- <h1>Please Login</h1>
- <form action="%s" method="post">
- <dl>
- <dt>Username:</dt>
- <dd><input type="text" name="username"></dd>
- <dt>Password:</dt>
- <dd><input type="password" name="password"></dd>
- </dl>
- <input type="submit" name="authform" />
- <hr />
- </form>
- </body>
-</html>
-"""
-
-class AuthFormHandler(object):
- """
- HTML-based login middleware
-
- This causes a HTML form to be returned if ``REMOTE_USER`` is
- not found in the ``environ``. If the form is returned, the
- ``username`` and ``password`` combination are given to a
- user-supplied authentication function, ``authfunc``. If this
- is successful, then application processing continues.
-
- Parameters:
-
- ``application``
-
- The application object is called only upon successful
- authentication, and can assume ``environ['REMOTE_USER']``
- is set. If the ``REMOTE_USER`` is already set, this
- middleware is simply pass-through.
-
- ``authfunc``
-
- This is a mandatory user-defined function which takes a
- ``environ``, ``username`` and ``password`` for its first
- three arguments. It should return ``True`` if the user is
- authenticated.
-
- ``template``
-
- This is an optional (a default is provided) HTML
- fragment that takes exactly one ``%s`` substution
- argument; which *must* be used for the form's ``action``
- to ensure that this middleware component does not alter
- the current path. The HTML form must use ``POST`` and
- have two input names: ``username`` and ``password``.
-
- Since the authentication form is submitted (via ``POST``)
- neither the ``PATH_INFO`` nor the ``QUERY_STRING`` are accessed,
- and hence the current path remains _unaltered_ through the
- entire authentication process. If authentication succeeds, the
- ``REQUEST_METHOD`` is converted from a ``POST`` to a ``GET``,
- so that a redirect is unnecessary (unlike most form auth
- implementations)
- """
-
- def __init__(self, application, authfunc, template=None):
- self.application = application
- self.authfunc = authfunc
- self.template = template or TEMPLATE
-
- def __call__(self, environ, start_response):
- username = environ.get('REMOTE_USER','')
- if username:
- return self.application(environ, start_response)
-
- if 'POST' == environ['REQUEST_METHOD']:
- formvars = parse_formvars(environ, include_get_vars=False)
- username = formvars.get('username')
- password = formvars.get('password')
- if username and password:
- if self.authfunc(environ, username, password):
- environ['AUTH_TYPE'] = 'form'
- environ['REMOTE_USER'] = username
- environ['REQUEST_METHOD'] = 'GET'
- environ['CONTENT_LENGTH'] = ''
- environ['CONTENT_TYPE'] = ''
- del environ['paste.parsed_formvars']
- return self.application(environ, start_response)
-
- content = self.template % construct_url(environ)
- start_response("200 OK", [('Content-Type', 'text/html'),
- ('Content-Length', str(len(content)))])
- return [content]
-
-middleware = AuthFormHandler
-
-__all__ = ['AuthFormHandler']
-
-def make_form(app, global_conf, realm, authfunc, **kw):
- """
- Grant access via form authentication
-
- Config looks like this::
-
- [filter:grant]
- use = egg:Paste#auth_form
- 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"
- template = kw.get('template')
- if template is not None:
- template = eval_import(template)
- assert isinstance(template, str), "template must resolve to a string"
-
- return AuthFormHandler(app, authfunc, template)
-
-if "__main__" == __name__:
- import doctest
- doctest.testmod(optionflags=doctest.ELLIPSIS)
diff --git a/lib/paste/auth/grantip.py b/lib/paste/auth/grantip.py
@@ -1,113 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Grant roles and logins based on IP address.
-"""
-from paste.util import ip4
-
-class GrantIPMiddleware(object):
-
- """
- On each request, ``ip_map`` is checked against ``REMOTE_ADDR``
- and logins and roles are assigned based on that.
-
- ``ip_map`` is a map of {ip_mask: (username, roles)}. Either
- ``username`` or ``roles`` may be None. Roles may also be prefixed
- with ``-``, like ``'-system'`` meaning that role should be
- revoked. ``'__remove__'`` for a username will remove the username.
-
- If ``clobber_username`` is true (default) then any user
- specification will override the current value of ``REMOTE_USER``.
- ``'__remove__'`` will always clobber the username.
-
- ``ip_mask`` is something that `paste.util.ip4:IP4Range
- <class-paste.util.ip4.IP4Range.html>`_ can parse. Simple IP
- addresses, IP/mask, ip<->ip ranges, and hostnames are allowed.
- """
-
- def __init__(self, app, ip_map, clobber_username=True):
- self.app = app
- self.ip_map = []
- for key, value in ip_map.items():
- self.ip_map.append((ip4.IP4Range(key),
- self._convert_user_role(value[0], value[1])))
- self.clobber_username = clobber_username
-
- def _convert_user_role(self, username, roles):
- if roles and isinstance(roles, basestring):
- roles = roles.split(',')
- return (username, roles)
-
- def __call__(self, environ, start_response):
- addr = ip4.ip2int(environ['REMOTE_ADDR'], False)
- remove_user = False
- add_roles = []
- for range, (username, roles) in self.ip_map:
- if addr in range:
- if roles:
- add_roles.extend(roles)
- if username == '__remove__':
- remove_user = True
- elif username:
- if (not environ.get('REMOTE_USER')
- or self.clobber_username):
- environ['REMOTE_USER'] = username
- if (remove_user and 'REMOTE_USER' in environ):
- del environ['REMOTE_USER']
- if roles:
- self._set_roles(environ, add_roles)
- return self.app(environ, start_response)
-
- 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)
- remove_roles = []
- for role in roles:
- if role.startswith('-'):
- remove_roles.append(role[1:])
- else:
- if role not in cur_roles:
- cur_roles.append(role)
- for role in remove_roles:
- 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.
-
- Config looks like this::
-
- [filter:grant]
- use = egg:Paste#grantip
- clobber_username = true
- # Give localhost system role (no username):
- 127.0.0.1 = -:system
- # Give everyone in 192.168.0.* editor role:
- 192.168.0.0/24 = -:editor
- # Give one IP the username joe:
- 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)
- ip_map = {}
- for key, value in kw.items():
- if ':' in value:
- username, role = value.split(':', 1)
- else:
- username = value
- role = ''
- if username == '-':
- username = ''
- if role == '-':
- role = ''
- ip_map[key] = value
- return GrantIPMiddleware(app, ip_map, clobber_username)
-
-
diff --git a/lib/paste/auth/multi.py b/lib/paste/auth/multi.py
@@ -1,79 +0,0 @@
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-Authentication via Multiple Methods
-
-In some environments, the choice of authentication method to be used
-depends upon the environment and is not "fixed". This middleware allows
-N authentication methods to be registered along with a goodness function
-which determines which method should be used. The following example
-demonstrates how to use both form and digest authentication in a server
-stack; by default it uses form-based authentication unless
-``*authmeth=digest`` is specified as a query argument.
-
->>> from paste.auth import form, cookie, digest, multi
->>> from paste.wsgilib import dump_environ
->>> from paste.httpserver import serve
->>>
->>> multi = multi.MultiHandler(dump_environ)
->>> def authfunc(environ, realm, user):
-... return digest.digest_password(realm, user, user)
->>> multi.add_method('digest', digest.middleware, "Test Realm", authfunc)
->>> multi.set_query_argument('digest')
->>>
->>> def authfunc(environ, username, password):
-... return username == password
->>> multi.add_method('form', form.middleware, authfunc)
->>> multi.set_default('form')
->>> serve(cookie.middleware(multi))
-serving on...
-
-"""
-
-class MultiHandler(object):
- """
- Multiple Authentication Handler
-
- This middleware provides two othogonal facilities:
-
- - a manner to register any number of authentication middlewares
-
- - a mechanism to register predicates which cause one of the
- registered middlewares to be used depending upon the request
-
- If none of the predicates returns True, then the application is
- invoked directly without middleware
- """
- def __init__(self, application):
- self.application = application
- self.default = application
- self.binding = {}
- self.predicate = []
- def add_method(self, name, factory, *args, **kwargs):
- self.binding[name] = factory(self.application, *args, **kwargs)
- def add_predicate(self, name, checker):
- self.predicate.append((checker, self.binding[name]))
- def set_default(self, name):
- """ set default authentication method """
- self.default = self.binding[name]
- def set_query_argument(self, name, key = '*authmeth', value = None):
- """ choose authentication method based on a query argument """
- lookfor = "%s=%s" % (key, value or name)
- self.add_predicate(name,
- lambda environ: lookfor in environ.get('QUERY_STRING',''))
- def __call__(self, environ, start_response):
- for (checker, binding) in self.predicate:
- if checker(environ):
- return binding(environ, start_response)
- return self.default(environ, start_response)
-
-middleware = MultiHandler
-
-__all__ = ['MultiHandler']
-
-if "__main__" == __name__:
- import doctest
- doctest.testmod(optionflags=doctest.ELLIPSIS)
-
diff --git a/lib/paste/auth/open_id.py b/lib/paste/auth/open_id.py
@@ -1,412 +0,0 @@
-# (c) 2005 Ben Bangert
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-"""
-OpenID Authentication (Consumer)
-
-OpenID is a distributed authentication system for single sign-on originally
-developed at/for LiveJournal.com.
-
- http://openid.net/
-
-URL. You can have multiple identities in the same way you can have multiple
-URLs. All OpenID does is provide a way to prove that you own a URL (identity).
-And it does this without passing around your password, your email address, or
-anything you don't want it to. There's no profile exchange component at all:
-your profiile is your identity URL, but recipients of your identity can then
-learn more about you from any public, semantically interesting documents
-linked thereunder (FOAF, RSS, Atom, vCARD, etc.).
-
-``Note``: paste.auth.openid requires installation of the Python-OpenID
-libraries::
-
- http://www.openidenabled.com/
-
-This module is based highly off the consumer.py that Python OpenID comes with.
-
-Using the OpenID Middleware
-===========================
-
-Using the OpenID middleware is fairly easy, the most minimal example using the
-basic login form thats included::
-
- # Add to your wsgi app creation
- from paste.auth import open_id
-
- wsgi_app = open_id.middleware(wsgi_app, '/somewhere/to/store/openid/data')
-
-You will now have the OpenID form available at /oid on your site. Logging in will
-verify that the login worked.
-
-A more complete login should involve having the OpenID middleware load your own
-login page after verifying the OpenID URL so that you can retain the login
-information in your webapp (session, cookies, etc.)::
-
- wsgi_app = open_id.middleware(wsgi_app, '/somewhere/to/store/openid/data',
- login_redirect='/your/login/code')
-
-Your login code should then be configured to retrieve 'paste.auth.open_id' for
-the users OpenID URL. If this key does not exist, the user has not logged in.
-
-Once the login is retrieved, it should be saved in your webapp, and the user
-should be redirected to wherever they would normally go after a successful
-login.
-"""
-
-__all__ = ['AuthOpenIDHandler']
-
-import cgi
-import urlparse
-import re
-
-import paste.request
-from paste import httpexceptions
-
-def quoteattr(s):
- qs = cgi.escape(s, 1)
- return '"%s"' % (qs,)
-
-# You may need to manually add the openid package into your
-# python path if you don't have it installed with your system python.
-# If so, uncomment the line below, and change the path where you have
-# Python-OpenID.
-# sys.path.append('/path/to/openid/')
-
-from openid.store import filestore
-from openid.consumer import consumer
-from openid.oidutil import appendArgs
-
-class AuthOpenIDHandler(object):
- """
- This middleware implements OpenID Consumer behavior to authenticate a
- URL against an OpenID Server.
- """
-
- def __init__(self, app, data_store_path, auth_prefix='/oid',
- login_redirect=None, catch_401=False,
- url_to_username=None):
- """
- Initialize the OpenID middleware
-
- ``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.
- """
- store = filestore.FileOpenIDStore(data_store_path)
- self.oidconsumer = consumer.OpenIDConsumer(store)
-
- self.app = app
- self.auth_prefix = auth_prefix
- self.data_store_path = data_store_path
- self.login_redirect = login_redirect
- self.catch_401 = catch_401
- self.url_to_username = url_to_username
-
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'].startswith(self.auth_prefix):
- # Let's load everything into a request dict to pass around easier
- request = dict(environ=environ, start=start_response, body=[])
- request['base_url'] = paste.request.construct_url(environ, with_path_info=False,
- with_query_string=False)
-
- path = re.sub(self.auth_prefix, '', environ['PATH_INFO'])
- request['parsed_uri'] = urlparse.urlparse(path)
- request['query'] = dict(paste.request.parse_querystring(environ))
-
- path = request['parsed_uri'][2]
- if path == '/' or not path:
- return self.render(request)
- elif path == '/verify':
- return self.do_verify(request)
- elif path == '/process':
- return self.do_process(request)
- else:
- return self.not_found(request)
- else:
- if self.catch_401:
- return self.catch_401_app_call(environ, start_response)
- return self.app(environ, start_response)
-
- def catch_401_app_call(self, environ, start_response):
- """
- Call the application, and redirect if the app returns a 401 response
- """
- was_401 = []
- def replacement_start_response(status, headers, exc_info=None):
- if int(status.split(None, 1)) == 401:
- # @@: Do I need to append something to go back to where we
- # came from?
- was_401.append(1)
- def dummy_writer(v):
- pass
- return dummy_writer
- else:
- return start_response(status, headers, exc_info)
- app_iter = self.app(environ, replacement_start_response)
- if was_401:
- try:
- list(app_iter)
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- redir_url = paste.request.construct_url(environ, with_path_info=False,
- with_query_string=False)
- exc = httpexceptions.HTTPTemporaryRedirect(redir_url)
- return exc.wsgi_application(environ, start_response)
- else:
- return app_iter
-
- def do_verify(self, request):
- """Process the form submission, initating OpenID verification.
- """
-
- # First, make sure that the user entered something
- openid_url = request['query'].get('openid_url')
- if not openid_url:
- return self.render(request, 'Enter an identity URL to verify.',
- css_class='error', form_contents=openid_url)
-
- oidconsumer = self.oidconsumer
-
- # Then, ask the library to begin the authorization.
- # Here we find out the identity server that will verify the
- # user's identity, and get a token that allows us to
- # communicate securely with the identity server.
- status, info = oidconsumer.beginAuth(openid_url)
-
- # If the URL was unusable (either because of network
- # conditions, a server error, or that the response returned
- # was not an OpenID identity page), the library will return
- # an error code. Let the user know that that URL is unusable.
- if status in [consumer.HTTP_FAILURE, consumer.PARSE_ERROR]:
- if status == consumer.HTTP_FAILURE:
- fmt = 'Failed to retrieve <q>%s</q>'
- else:
- fmt = 'Could not find OpenID information in <q>%s</q>'
-
- message = fmt % (cgi.escape(openid_url),)
- return self.render(request, message, css_class='error', form_contents=openid_url)
- elif status == consumer.SUCCESS:
- # The URL was a valid identity URL. Now we construct a URL
- # that will get us to process the server response. We will
- # need the token from the beginAuth call when processing
- # the response. A cookie or a session object could be used
- # to accomplish this, but for simplicity here we just add
- # it as a query parameter of the return-to URL.
- return_to = self.build_url(request, 'process', token=info.token)
-
- # Now ask the library for the URL to redirect the user to
- # his OpenID server. It is required for security that the
- # return_to URL must be under the specified trust_root. We
- # just use the base_url for this server as a trust root.
- redirect_url = oidconsumer.constructRedirect(
- info, return_to, trust_root=request['base_url'])
-
- # Send the redirect response
- return self.redirect(request, redirect_url)
- else:
- assert False, 'Not reached'
-
- def do_process(self, request):
- """Handle the redirect from the OpenID server.
- """
- oidconsumer = self.oidconsumer
-
- # retrieve the token from the environment (in this case, the URL)
- token = request['query'].get('token', '')
-
- # Ask the library to check the response that the server sent
- # us. Status is a code indicating the response type. info is
- # either None or a string containing more information about
- # the return type.
- status, info = oidconsumer.completeAuth(token, request['query'])
-
- css_class = 'error'
- openid_url = None
- if status == consumer.FAILURE and info:
- # In the case of failure, if info is non-None, it is the
- # URL that we were verifying. We include it in the error
- # message to help the user figure out what happened.
- openid_url = info
- fmt = "Verification of %s failed."
- message = fmt % (cgi.escape(openid_url),)
- elif status == consumer.SUCCESS:
- # Success means that the transaction completed without
- # error. If info is None, it means that the user cancelled
- # the verification.
- css_class = 'alert'
- if info:
- # This is a successful verification attempt. If this
- # was a real application, we would do our login,
- # comment posting, etc. here.
- openid_url = info
- if self.url_to_username:
- username = self.url_to_username(request['environ'], openid_url)
- else:
- username = openid_url
- if 'paste.auth_tkt.set_user' in request['environ']:
- request['environ']['paste.auth_tkt.set_user'](username)
- if not self.login_redirect:
- fmt = ("If you had supplied a login redirect path, you would have "
- "been redirected there. "
- "You have successfully verified %s as your identity.")
- message = fmt % (cgi.escape(openid_url),)
- else:
- # @@: This stuff doesn't make sense to me; why not a remote redirect?
- request['environ']['paste.auth.open_id'] = openid_url
- request['environ']['PATH_INFO'] = self.login_redirect
- return self.app(request['environ'], request['start'])
- #exc = httpexceptions.HTTPTemporaryRedirect(self.login_redirect)
- #return exc.wsgi_application(request['environ'], request['start'])
- else:
- # cancelled
- message = 'Verification cancelled'
- else:
- # Either we don't understand the code or there is no
- # openid_url included with the error. Give a generic
- # failure message. The library should supply debug
- # information in a log.
- message = 'Verification failed.'
-
- return self.render(request, message, css_class, openid_url)
-
- def build_url(self, request, action, **query):
- """Build a URL relative to the server base_url, with the given
- query parameters added."""
- base = urlparse.urljoin(request['base_url'], self.auth_prefix + '/' + action)
- return appendArgs(base, query)
-
- def redirect(self, request, redirect_url):
- """Send a redirect response to the given URL to the browser."""
- response_headers = [('Content-type', 'text/plain'),
- ('Location', redirect_url)]
- request['start']('302 REDIRECT', response_headers)
- return ["Redirecting to %s" % redirect_url]
-
- def not_found(self, request):
- """Render a page with a 404 return code and a message."""
- fmt = 'The path <q>%s</q> was not understood by this server.'
- msg = fmt % (request['parsed_uri'],)
- openid_url = request['query'].get('openid_url')
- return self.render(request, msg, 'error', openid_url, status='404 Not Found')
-
- def render(self, request, message=None, css_class='alert', form_contents=None,
- status='200 OK', title="Python OpenID Consumer"):
- """Render a page."""
- response_headers = [('Content-type', 'text/html')]
- request['start'](str(status), response_headers)
-
- self.page_header(request, title)
- if message:
- request['body'].append("<div class='%s'>" % (css_class,))
- request['body'].append(message)
- request['body'].append("</div>")
- self.page_footer(request, form_contents)
- return request['body']
-
- def page_header(self, request, title):
- """Render the page header"""
- request['body'].append('''\
-<html>
- <head><title>%s</title></head>
- <style type="text/css">
- * {
- font-family: verdana,sans-serif;
- }
- body {
- width: 50em;
- margin: 1em;
- }
- div {
- padding: .5em;
- }
- table {
- margin: none;
- padding: none;
- }
- .alert {
- border: 1px solid #e7dc2b;
- background: #fff888;
- }
- .error {
- border: 1px solid #ff0000;
- background: #ffaaaa;
- }
- #verify-form {
- border: 1px solid #777777;
- background: #dddddd;
- margin-top: 1em;
- padding-bottom: 0em;
- }
- </style>
- <body>
- <h1>%s</h1>
- <p>
- This example consumer uses the <a
- href="http://openid.schtuff.com/">Python OpenID</a> library. It
- just verifies that the URL that you enter is your identity URL.
- </p>
-''' % (title, title))
-
- def page_footer(self, request, form_contents):
- """Render the page footer"""
- if not form_contents:
- form_contents = ''
-
- request['body'].append('''\
- <div id="verify-form">
- <form method="get" action=%s>
- Identity URL:
- <input type="text" name="openid_url" value=%s />
- <input type="submit" value="Verify" />
- </form>
- </div>
- </body>
-</html>
-''' % (quoteattr(self.build_url(request, 'verify')), quoteattr(form_contents)))
-
-
-middleware = AuthOpenIDHandler
-
-def make_open_id_middleware(
- app,
- global_conf,
- # Should this default to something, or inherit something from global_conf?:
- data_store_path,
- auth_prefix='/oid',
- login_redirect=None,
- catch_401=False,
- url_to_username=None,
- apply_auth_tkt=False,
- auth_tkt_logout_path=None):
- from paste.deploy.converters import asbool
- from paste.util import import_string
- catch_401 = asbool(catch_401)
- if url_to_username and isinstance(url_to_username, basestring):
- url_to_username = import_string.eval_import(url_to_username)
- apply_auth_tkt = asbool(apply_auth_tkt)
- new_app = AuthOpenIDHandler(
- app, data_store_path=data_store_path, auth_prefix=auth_prefix,
- login_redirect=login_redirect, catch_401=catch_401,
- url_to_username=url_to_username or None)
- if apply_auth_tkt:
- from paste.auth import auth_tkt
- new_app = auth_tkt.make_auth_tkt_middleware(
- new_app, global_conf, logout_path=auth_tkt_logout_path)
- return new_app
diff --git a/lib/paste/cascade.py b/lib/paste/cascade.py
@@ -1,133 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-Cascades through several applications, so long as applications
-return ``404 Not Found``.
-"""
-from paste import httpexceptions
-from paste.util import converters
-import tempfile
-from cStringIO import StringIO
-
-__all__ = ['Cascade']
-
-def make_cascade(loader, global_conf, catch='404', **local_conf):
- """
- Entry point for Paste Deploy configuration
-
- Expects configuration like::
-
- [composit:cascade]
- use = egg:Paste#cascade
- # all start with 'app' and are sorted alphabetically
- app1 = foo
- app2 = bar
- ...
- catch = 404 500 ...
- """
- catch = map(int, converters.aslist(catch))
- apps = []
- for name, value in local_conf.items():
- if not name.startswith('app'):
- raise ValueError(
- "Bad configuration key %r (=%r); all configuration keys "
- "must start with 'app'"
- % (name, value))
- app = loader.get_app(value, global_conf=global_conf)
- apps.append((name, app))
- apps.sort()
- apps = [app for name, app in apps]
- return Cascade(apps, catch=catch)
-
-class Cascade(object):
-
- """
- Passed a list of applications, ``Cascade`` will try each of them
- in turn. If one returns a status code listed in ``catch`` (by
- default just ``404 Not Found``) then the next application is
- tried.
-
- If all applications fail, then the last application's failure
- response is used.
-
- Instances of this class are WSGI applications.
- """
-
- def __init__(self, applications, catch=(404,)):
- self.apps = applications
- self.catch_codes = {}
- self.catch_exceptions = []
- for error in catch:
- if isinstance(error, str):
- error = int(error.split(None, 1)[0])
- if isinstance(error, httpexceptions.HTTPException):
- exc = error
- code = error.code
- else:
- exc = httpexceptions.get_exception(error)
- code = error
- 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
- """
- failed = []
- def repl_start_response(status, headers, exc_info=None):
- code = int(status.split(None, 1)[0])
- if code in self.catch_codes:
- failed.append(None)
- return _consuming_writer
- return start_response(status, headers, exc_info)
-
- try:
- length = int(environ.get('CONTENT_LENGTH', 0) or 0)
- except ValueError:
- length = 0
- if length > 0:
- # We have to copy wsgi.input
- copy_wsgi_input = True
- if length > 4096 or length < 0:
- f = tempfile.TemporaryFile()
- if length < 0:
- f.write(environ['wsgi.input'].read())
- else:
- copy_len = length
- while copy_len > 0:
- chunk = environ['wsgi.input'].read(min(copy_len, 4096))
- if not chunk:
- raise IOError("Request body truncated")
- f.write(chunk)
- copy_len -= len(chunk)
- f.seek(0)
- else:
- f = StringIO(environ['wsgi.input'].read(length))
- environ['wsgi.input'] = f
- else:
- copy_wsgi_input = False
- for app in self.apps[:-1]:
- environ_copy = environ.copy()
- if copy_wsgi_input:
- environ_copy['wsgi.input'].seek(0)
- failed = []
- try:
- v = app(environ_copy, repl_start_response)
- if not failed:
- return v
- else:
- if hasattr(v, 'close'):
- # Exhaust the iterator first:
- list(v)
- # then close:
- v.close()
- except self.catch_exceptions, e:
- pass
- if copy_wsgi_input:
- environ['wsgi.input'].seek(0)
- return self.apps[-1](environ, start_response)
-
-def _consuming_writer(s):
- pass
diff --git a/lib/paste/cgiapp.py b/lib/paste/cgiapp.py
@@ -1,271 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-Application that runs a CGI script.
-"""
-import os
-import subprocess
-try:
- import select
-except ImportError:
- select = None
-
-from paste.util import converters
-
-__all__ = ['CGIError', 'CGIApplication']
-
-class CGIError(Exception):
- """
- Raised when the CGI script can't be found or doesn't
- act like a proper CGI script.
- """
-
-class CGIApplication(object):
-
- """
- This object acts as a proxy to a CGI application. You pass in the
- script path (``script``), an optional path to search for the
- script (if the name isn't absolute) (``path``). If you don't give
- a path, then ``$PATH`` will be used.
- """
-
- def __init__(self,
- global_conf,
- script,
- path=None,
- include_os_environ=True,
- query_string=None):
- if global_conf:
- raise NotImplemented(
- "global_conf is no longer supported for CGIApplication "
- "(use make_cgi_application); please pass None instead")
- self.script_filename = script
- if path is None:
- path = os.environ.get('PATH', '').split(':')
- self.path = path
- if '?' in script:
- assert query_string is None, (
- "You cannot have '?' in your script name (%r) and also "
- "give a query_string (%r)" % (script, query_string))
- script, query_string = script.split('?', 1)
- if os.path.abspath(script) != script:
- # relative path
- for path_dir in self.path:
- if os.path.exists(os.path.join(path_dir, script)):
- self.script = os.path.join(path_dir, script)
- break
- else:
- raise CGIError(
- "Script %r not found in path %r"
- % (script, self.path))
- else:
- self.script = script
- self.include_os_environ = include_os_environ
- self.query_string = query_string
-
- def __call__(self, environ, start_response):
- if 'REQUEST_URI' not in environ:
- environ['REQUEST_URI'] = (
- environ.get('SCRIPT_NAME', '')
- + environ.get('PATH_INFO', ''))
- if self.include_os_environ:
- cgi_environ = os.environ.copy()
- else:
- cgi_environ = {}
- for name in environ:
- # Should unicode values be encoded?
- if (name.upper() == name
- and isinstance(environ[name], str)):
- cgi_environ[name] = environ[name]
- if self.query_string is not None:
- old = cgi_environ.get('QUERY_STRING', '')
- if old:
- old += '&'
- cgi_environ['QUERY_STRING'] = old + self.query_string
- cgi_environ['SCRIPT_FILENAME'] = self.script
- proc = subprocess.Popen(
- [self.script],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- env=cgi_environ,
- cwd=os.path.dirname(self.script),
- )
- writer = CGIWriter(environ, start_response)
- if select:
- proc_communicate(
- proc,
- stdin=StdinReader.from_environ(environ),
- stdout=writer,
- stderr=environ['wsgi.errors'])
- else:
- stdout, stderr = proc.communicate(StdinReader.from_environ(environ).read())
- if stderr:
- environ['wsgi.errors'].write(stderr)
- writer(stdout)
- if not writer.headers_finished:
- start_response(writer.status, writer.headers)
- return []
-
-class CGIWriter(object):
-
- def __init__(self, environ, start_response):
- self.environ = environ
- self.start_response = start_response
- self.status = '200 OK'
- self.headers = []
- self.headers_finished = False
- self.writer = None
- self.buffer = ''
-
- 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:
- line1, self.buffer = self.buffer.split('\r\n', 1)
- else:
- line1, self.buffer = self.buffer.split('\n', 1)
- if not line1:
- self.headers_finished = True
- self.writer = self.start_response(
- self.status, self.headers)
- self.writer(self.buffer)
- del self.buffer
- del self.headers
- del self.status
- break
- elif ':' not in line1:
- raise CGIError(
- "Bad header line: %r" % line1)
- else:
- name, value = line1.split(':', 1)
- value = value.lstrip()
- name = name.strip()
- if name.lower() == 'status':
- self.status = value
- else:
- self.headers.append((name, value))
-
-class StdinReader(object):
-
- def __init__(self, stdin, content_length):
- self.stdin = stdin
- self.content_length = content_length
-
- def from_environ(cls, environ):
- length = environ.get('CONTENT_LENGTH')
- if length:
- length = int(length)
- else:
- length = 0
- return cls(environ['wsgi.input'], length)
-
- from_environ = classmethod(from_environ)
-
- def read(self, size=None):
- if not self.content_length:
- return ''
- if size is None:
- text = self.stdin.read(self.content_length)
- else:
- text = self.stdin.read(min(self.content_length, size))
- self.content_length -= len(text)
- return text
-
-def proc_communicate(proc, stdin=None, stdout=None, stderr=None):
- """
- Run the given process, piping input/output/errors to the given
- file-like objects (which need not be actual file objects, unlike
- the arguments passed to Popen). Wait for process to terminate.
-
- Note: this is taken from the posix version of
- subprocess.Popen.communicate, but made more general through the
- use of file-like objects.
- """
- read_set = []
- write_set = []
- input_buffer = ''
- trans_nl = proc.universal_newlines and hasattr(open, 'newlines')
-
- if proc.stdin:
- # Flush stdio buffer. This might block, if the user has
- # been writing to .stdin in an uncontrolled fashion.
- proc.stdin.flush()
- if input:
- write_set.append(proc.stdin)
- else:
- proc.stdin.close()
- else:
- assert stdin is None
- if proc.stdout:
- read_set.append(proc.stdout)
- else:
- assert stdout is None
- if proc.stderr:
- read_set.append(proc.stderr)
- else:
- assert stderr is None
-
- while read_set or write_set:
- rlist, wlist, xlist = select.select(read_set, write_set, [])
-
- if proc.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
- next, input_buffer = input_buffer, ''
- next_len = 512-len(next)
- if next_len:
- next += stdin.read(next_len)
- if not next:
- proc.stdin.close()
- write_set.remove(proc.stdin)
- else:
- bytes_written = os.write(proc.stdin.fileno(), next)
- if bytes_written < len(next):
- input_buffer = next[bytes_written:]
-
- if proc.stdout in rlist:
- data = os.read(proc.stdout.fileno(), 1024)
- if data == "":
- proc.stdout.close()
- read_set.remove(proc.stdout)
- if trans_nl:
- data = proc._translate_newlines(data)
- stdout.write(data)
-
- if proc.stderr in rlist:
- data = os.read(proc.stderr.fileno(), 1024)
- if data == "":
- proc.stderr.close()
- read_set.remove(proc.stderr)
- if trans_nl:
- data = proc._translate_newlines(data)
- stderr.write(data)
-
- try:
- proc.wait()
- except OSError, e:
- if e.errno != 10:
- raise
-
-def make_cgi_application(global_conf, script, path=None, include_os_environ=None,
- query_string=None):
- """
- Paste Deploy interface for :class:`CGIApplication`
-
- This object acts as a proxy to a CGI application. You pass in the
- script path (``script``), an optional path to search for the
- script (if the name isn't absolute) (``path``). If you don't give
- a path, then ``$PATH`` will be used.
- """
- if path is None:
- path = global_conf.get('path') or global_conf.get('PATH')
- include_os_environ = converters.asbool(include_os_environ)
- return CGIApplication(
- script, path=path, include_os_environ=include_os_environ,
- query_string=query_string)
diff --git a/lib/paste/cgitb_catcher.py b/lib/paste/cgitb_catcher.py
@@ -1,116 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-WSGI middleware
-
-Captures any exceptions and prints a pretty report. See the `cgitb
-documentation <http://python.org/doc/current/lib/module-cgitb.html>`_
-for more.
-"""
-
-import cgitb
-from cStringIO import StringIO
-import sys
-
-from paste.util import converters
-
-class NoDefault(object):
- pass
-
-class CgitbMiddleware(object):
-
- def __init__(self, app,
- global_conf=None,
- display=NoDefault,
- logdir=None,
- context=5,
- format="html"):
- self.app = app
- if global_conf is None:
- global_conf = {}
- if display is NoDefault:
- display = global_conf.get('debug')
- if isinstance(display, basestring):
- display = converters.asbool(display)
- self.display = display
- self.logdir = logdir
- self.context = int(context)
- self.format = format
-
- def __call__(self, environ, start_response):
- try:
- app_iter = self.app(environ, start_response)
- return self.catching_iter(app_iter, environ)
- except:
- exc_info = sys.exc_info()
- start_response('500 Internal Server Error',
- [('content-type', 'text/html')],
- exc_info)
- response = self.exception_handler(exc_info, environ)
- return [response]
-
- def catching_iter(self, app_iter, environ):
- if not app_iter:
- raise StopIteration
- error_on_close = False
- try:
- for v in app_iter:
- yield v
- if hasattr(app_iter, 'close'):
- error_on_close = True
- app_iter.close()
- except:
- response = self.exception_handler(sys.exc_info(), environ)
- if not error_on_close and hasattr(app_iter, 'close'):
- try:
- app_iter.close()
- except:
- close_response = self.exception_handler(
- sys.exc_info(), environ)
- response += (
- '<hr noshade>Error in .close():<br>%s'
- % close_response)
- yield response
-
- def exception_handler(self, exc_info, environ):
- dummy_file = StringIO()
- hook = cgitb.Hook(file=dummy_file,
- display=self.display,
- logdir=self.logdir,
- context=self.context,
- format=self.format)
- hook(*exc_info)
- return dummy_file.getvalue()
-
-def make_cgitb_middleware(app, global_conf,
- display=NoDefault,
- logdir=None,
- context=5,
- format='html'):
- """
- 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
-
- logdir:
- Writes logs of all errors in that directory
-
- context:
- Number of lines of context to show around each line of
- source code
- """
- from paste.deploy.converters import asbool
- if display is not NoDefault:
- display = asbool(display)
- if 'debug' in global_conf:
- global_conf['debug'] = asbool(global_conf['debug'])
- return CgitbMiddleware(
- app, global_conf=global_conf,
- display=display,
- logdir=logdir,
- context=context,
- format=format)
diff --git a/lib/paste/config.py b/lib/paste/config.py
@@ -1,120 +0,0 @@
-# (c) 2006 Ian Bicking, Philip Jenvey and contributors
-# Written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""Paste Configuration Middleware and Objects"""
-from paste.registry import RegistryManager, StackedObjectProxy
-
-__all__ = ['DispatchingConfig', 'CONFIG', 'ConfigMiddleware']
-
-class DispatchingConfig(StackedObjectProxy):
- """
- This is a configuration object that can be used globally,
- imported, have references held onto. The configuration may differ
- by thread (or may not).
-
- Specific configurations are registered (and deregistered) either
- for the process or for threads.
- """
- # @@: What should happen when someone tries to add this
- # configuration to itself? Probably the conf should become
- # resolved, and get rid of this delegation wrapper
-
- def __init__(self, name='DispatchingConfig'):
- super(DispatchingConfig, self).__init__(name=name)
- self.__dict__['_process_configs'] = []
-
- def push_thread_config(self, conf):
- """
- Make ``conf`` the active configuration for this thread.
- Thread-local configuration always overrides process-wide
- configuration.
-
- This should be used like::
-
- conf = make_conf()
- dispatching_config.push_thread_config(conf)
- try:
- ... do stuff ...
- finally:
- dispatching_config.pop_thread_config(conf)
- """
- self._push_object(conf)
-
- def pop_thread_config(self, conf=None):
- """
- Remove a thread-local configuration. If ``conf`` is given,
- it is checked against the popped configuration and an error
- is emitted if they don't match.
- """
- self._pop_object(conf)
-
- def push_process_config(self, conf):
- """
- Like push_thread_config, but applies the configuration to
- the entire process.
- """
- self._process_configs.append(conf)
-
- def pop_process_config(self, conf=None):
- self._pop_from(self._process_configs, conf)
-
- def _pop_from(self, lst, conf):
- popped = lst.pop()
- if conf is not None and popped is not conf:
- raise AssertionError(
- "The config popped (%s) is not the same as the config "
- "expected (%s)"
- % (popped, conf))
-
- def _current_obj(self):
- try:
- return super(DispatchingConfig, self)._current_obj()
- except TypeError:
- if self._process_configs:
- return self._process_configs[-1]
- raise AttributeError(
- "No configuration has been registered for this process "
- "or thread")
- current = current_conf = _current_obj
-
-CONFIG = DispatchingConfig()
-
-no_config = object()
-class ConfigMiddleware(RegistryManager):
- """
- A WSGI middleware that adds a ``paste.config`` key (by default)
- to the request environment, as well as registering the
- configuration temporarily (for the length of the request) with
- ``paste.config.CONFIG`` (or any other ``DispatchingConfig``
- object).
- """
-
- def __init__(self, application, config, dispatching_config=CONFIG,
- environ_key='paste.config'):
- """
- This delegates all requests to `application`, adding a *copy*
- of the configuration `config`.
- """
- def register_config(environ, start_response):
- popped_config = environ.get(environ_key, no_config)
- current_config = environ[environ_key] = config.copy()
- environ['paste.registry'].register(dispatching_config,
- current_config)
-
- try:
- app_iter = application(environ, start_response)
- finally:
- if popped_config is no_config:
- environ.pop(environ_key, None)
- else:
- environ[environ_key] = popped_config
- return app_iter
-
- super(self.__class__, self).__init__(register_config)
-
-def make_config_filter(app, global_conf, **local_conf):
- conf = global_conf.copy()
- conf.update(local_conf)
- return ConfigMiddleware(app, conf)
-
-make_config_middleware = ConfigMiddleware.__doc__
diff --git a/lib/paste/debug/__init__.py b/lib/paste/debug/__init__.py
@@ -1,5 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Package for debugging and development tools
-"""
diff --git a/lib/paste/debug/debugapp.py b/lib/paste/debug/debugapp.py
@@ -1,79 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-Various Applications for Debugging/Testing Purposes
-"""
-
-import time
-__all__ = ['SimpleApplication', 'SlowConsumer']
-
-
-class SimpleApplication(object):
- """
- Produces a simple web page
- """
- def __call__(self, environ, start_response):
- body = "<html><body>simple</body></html>"
- start_response("200 OK", [('Content-Type', 'text/html'),
- ('Content-Length', str(len(body)))])
- return [body]
-
-class SlowConsumer(object):
- """
- Consumes an upload slowly...
-
- NOTE: This should use the iterator form of ``wsgi.input``,
- but it isn't implemented in paste.httpserver.
- """
- def __init__(self, chunk_size = 4096, delay = 1, progress = True):
- self.chunk_size = chunk_size
- self.delay = delay
- self.progress = True
-
- def __call__(self, environ, start_response):
- size = 0
- total = environ.get('CONTENT_LENGTH')
- if total:
- remaining = int(total)
- while remaining > 0:
- if self.progress:
- print "%s of %s remaining" % (remaining, total)
- if remaining > 4096:
- chunk = environ['wsgi.input'].read(4096)
- else:
- chunk = environ['wsgi.input'].read(remaining)
- if not chunk:
- break
- size += len(chunk)
- remaining -= len(chunk)
- if self.delay:
- time.sleep(self.delay)
- body = "<html><body>%d bytes</body></html>" % size
- else:
- body = ('<html><body>\n'
- '<form method="post" enctype="multipart/form-data">\n'
- '<input type="file" name="file">\n'
- '<input type="submit" >\n'
- '</form></body></html>\n')
- print "bingles"
- start_response("200 OK", [('Content-Type', 'text/html'),
- ('Content-Length', len(body))])
- return [body]
-
-def make_test_app(global_conf):
- return SimpleApplication()
-
-make_test_app.__doc__ = SimpleApplication.__doc__
-
-def make_slow_app(global_conf, chunk_size=4096, delay=1, progress=True):
- from paste.deploy.converters import asbool
- return SlowConsumer(
- chunk_size=int(chunk_size),
- delay=int(delay),
- progress=asbool(progress))
-
-make_slow_app.__doc__ = SlowConsumer.__doc__
diff --git a/lib/paste/debug/doctest_webapp.py b/lib/paste/debug/doctest_webapp.py
@@ -1,435 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-#!/usr/bin/env python2.4
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-These are functions for use when doctest-testing a document.
-"""
-
-try:
- import subprocess
-except ImportError:
- from paste.util import subprocess24 as subprocess
-import doctest
-import os
-import sys
-import shutil
-import re
-import cgi
-import rfc822
-from cStringIO import StringIO
-from paste.util import PySourceColor
-
-
-here = os.path.abspath(__file__)
-paste_parent = os.path.dirname(
- os.path.dirname(os.path.dirname(here)))
-
-def run(command):
- data = run_raw(command)
- if data:
- print data
-
-def run_raw(command):
- """
- Runs the string command, returns any output.
- """
- proc = subprocess.Popen(command, shell=True,
- stderr=subprocess.STDOUT,
- stdout=subprocess.PIPE, env=_make_env())
- data = proc.stdout.read()
- proc.wait()
- while data.endswith('\n') or data.endswith('\r'):
- data = data[:-1]
- if data:
- data = '\n'.join(
- [l for l in data.splitlines() if l])
- return data
- else:
- return ''
-
-def run_command(command, name, and_print=False):
- output = run_raw(command)
- data = '$ %s\n%s' % (command, output)
- show_file('shell-command', name, description='shell transcript',
- data=data)
- if and_print and output:
- print output
-
-def _make_env():
- env = os.environ.copy()
- env['PATH'] = (env.get('PATH', '')
- + ':'
- + os.path.join(paste_parent, 'scripts')
- + ':'
- + os.path.join(paste_parent, 'paste', '3rd-party',
- 'sqlobject-files', 'scripts'))
- env['PYTHONPATH'] = (env.get('PYTHONPATH', '')
- + ':'
- + paste_parent)
- return env
-
-def clear_dir(dir):
- """
- Clears (deletes) the given directory
- """
- shutil.rmtree(dir, True)
-
-def ls(dir=None, recurse=False, indent=0):
- """
- Show a directory listing
- """
- dir = dir or os.getcwd()
- fns = os.listdir(dir)
- fns.sort()
- for fn in fns:
- full = os.path.join(dir, fn)
- if os.path.isdir(full):
- fn = fn + '/'
- print ' '*indent + fn
- if os.path.isdir(full) and recurse:
- ls(dir=full, recurse=True, indent=indent+2)
-
-default_app = None
-default_url = None
-
-def set_default_app(app, url):
- global default_app
- global default_url
- default_app = app
- default_url = url
-
-def resource_filename(fn):
- """
- Returns the filename of the resource -- generally in the directory
- resources/DocumentName/fn
- """
- return os.path.join(
- os.path.dirname(sys.testing_document_filename),
- 'resources',
- os.path.splitext(os.path.basename(sys.testing_document_filename))[0],
- fn)
-
-def show(path_info, example_name):
- fn = resource_filename(example_name + '.html')
- out = StringIO()
- assert default_app is not None, (
- "No default_app set")
- url = default_url + path_info
- out.write('<span class="doctest-url"><a href="%s">%s</a></span><br>\n'
- % (url, url))
- out.write('<div class="doctest-example">\n')
- proc = subprocess.Popen(
- ['paster', 'serve' '--server=console', '--no-verbose',
- '--url=' + path_info],
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- env=_make_env())
- stdout, errors = proc.communicate()
- stdout = StringIO(stdout)
- headers = rfc822.Message(stdout)
- content = stdout.read()
- for header, value in headers.items():
- if header.lower() == 'status' and int(value.split()[0]) == 200:
- continue
- if header.lower() in ('content-type', 'content-length'):
- continue
- if (header.lower() == 'set-cookie'
- and value.startswith('_SID_')):
- continue
- out.write('<span class="doctest-header">%s: %s</span><br>\n'
- % (header, value))
- lines = [l for l in content.splitlines() if l.strip()]
- for line in lines:
- out.write(line + '\n')
- if errors:
- out.write('<pre class="doctest-errors">%s</pre>'
- % errors)
- out.write('</div>\n')
- result = out.getvalue()
- if not os.path.exists(fn):
- f = open(fn, 'wb')
- f.write(result)
- f.close()
- else:
- f = open(fn, 'rb')
- expected = f.read()
- f.close()
- if not html_matches(expected, result):
- print 'Pages did not match. Expected from %s:' % fn
- print '-'*60
- print expected
- print '='*60
- print 'Actual output:'
- print '-'*60
- print result
-
-def html_matches(pattern, text):
- regex = re.escape(pattern)
- regex = regex.replace(r'\.\.\.', '.*')
- regex = re.sub(r'0x[0-9a-f]+', '.*', regex)
- regex = '^%s$' % regex
- return re.search(regex, text)
-
-def convert_docstring_string(data):
- if data.startswith('\n'):
- data = data[1:]
- lines = data.splitlines()
- new_lines = []
- for line in lines:
- if line.rstrip() == '.':
- new_lines.append('')
- else:
- new_lines.append(line)
- data = '\n'.join(new_lines) + '\n'
- return data
-
-def create_file(path, version, data):
- data = convert_docstring_string(data)
- write_data(path, data)
- show_file(path, version)
-
-def append_to_file(path, version, data):
- data = convert_docstring_string(data)
- f = open(path, 'a')
- f.write(data)
- f.close()
- # I think these appends can happen so quickly (in less than a second)
- # that the .pyc file doesn't appear to be expired, even though it
- # is after we've made this change; so we have to get rid of the .pyc
- # file:
- if path.endswith('.py'):
- pyc_file = path + 'c'
- if os.path.exists(pyc_file):
- os.unlink(pyc_file)
- show_file(path, version, description='added to %s' % path,
- data=data)
-
-def show_file(path, version, description=None, data=None):
- ext = os.path.splitext(path)[1]
- if data is None:
- f = open(path, 'rb')
- data = f.read()
- f.close()
- if ext == '.py':
- html = ('<div class="source-code">%s</div>'
- % PySourceColor.str2html(data, PySourceColor.dark))
- else:
- html = '<pre class="source-code">%s</pre>' % cgi.escape(data, 1)
- html = '<span class="source-filename">%s</span><br>%s' % (
- description or path, html)
- write_data(resource_filename('%s.%s.gen.html' % (path, version)),
- html)
-
-def call_source_highlight(input, format):
- proc = subprocess.Popen(['source-highlight', '--out-format=html',
- '--no-doc', '--css=none',
- '--src-lang=%s' % format], shell=False,
- stdout=subprocess.PIPE)
- stdout, stderr = proc.communicate(input)
- result = stdout
- proc.wait()
- return result
-
-
-def write_data(path, data):
- dir = os.path.dirname(os.path.abspath(path))
- if not os.path.exists(dir):
- os.makedirs(dir)
- f = open(path, 'wb')
- f.write(data)
- f.close()
-
-
-def change_file(path, changes):
- f = open(os.path.abspath(path), 'rb')
- lines = f.readlines()
- f.close()
- for change_type, line, text in changes:
- if change_type == 'insert':
- lines[line:line] = [text]
- elif change_type == 'delete':
- lines[line:text] = []
- else:
- assert 0, (
- "Unknown change_type: %r" % change_type)
- f = open(path, 'wb')
- f.write(''.join(lines))
- f.close()
-
-class LongFormDocTestParser(doctest.DocTestParser):
-
- """
- This parser recognizes some reST comments as commands, without
- prompts or expected output, like:
-
- .. run:
-
- do_this(...
- ...)
- """
-
- _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
- )*))
- |
- (?: # 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.
-
- (?:^\.\.[ ]*(?P<run>run):[ ]*\n) # Leading command/command
- (?:[ ]*\n)? # Blank line following
- (?P<runsource>
- (?:(?P<runindent> [ ]+)[^ ].*$)
- (?:\n [ ]+ .*)*)
- )
- |
- (?: # This is for shell commands
-
- (?P<shellsource>
- (?:^(P<shellindent> [ ]*) [$] .*) # Shell line
- (?:\n [ ]* [>] .*)*) # Continuation
- \n?
- # Want consists of any non-blank lines that do not start with $
- (?P<shellwant> (?:(?![ ]*$)
- (?![ ]*[$]$)
- .*$\n?
- )*))
- """, re.MULTILINE | re.VERBOSE)
-
- def _parse_example(self, m, name, lineno):
- r"""
- 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.
-
- >>> def parseit(s):
- ... p = LongFormDocTestParser()
- ... return p._parse_example(p._EXAMPLE_RE.search(s), '<string>', 1)
- >>> parseit('>>> 1\n1')
- ('1', {}, '1', None)
- >>> parseit('>>> (1\n... +1)\n2')
- ('(1\n+1)', {}, '2', None)
- >>> parseit('.. run:\n\n test1\n test2\n')
- ('test1\ntest2', {}, '', None)
- """
- # 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')
- if runner:
- self._check_prefix(source_lines[1:], ' '*indent, name, lineno)
- else:
- self._check_prompt_blank(source_lines, indent, name, lineno)
- self._check_prefix(source_lines[2:], ' '*indent + '.', name, lineno)
- if runner:
- source = '\n'.join([sl[indent:] for sl in source_lines])
- else:
- source = '\n'.join([sl[indent+4:] for sl in source_lines])
-
- if runner:
- want = ''
- exc_msg = None
- else:
- # 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
-
-
- 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):
- # @@: Erg, this is the only line I need to change...
- output.append(doctest.Example(
- source, want, exc_msg,
- lineno=lineno,
- indent=min_indent+len(m.group('indent') or m.group('runindent')),
- 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
-
-
-
-if __name__ == '__main__':
- if sys.argv[1:] and sys.argv[1] == 'doctest':
- doctest.testmod()
- sys.exit()
- if not paste_parent in sys.path:
- sys.path.append(paste_parent)
- for fn in sys.argv[1:]:
- fn = os.path.abspath(fn)
- # @@: OK, ick; but this module gets loaded twice
- sys.testing_document_filename = fn
- doctest.testfile(
- fn, module_relative=False,
- optionflags=doctest.ELLIPSIS|doctest.REPORT_ONLY_FIRST_FAILURE,
- parser=LongFormDocTestParser())
- new = os.path.splitext(fn)[0] + '.html'
- assert new != fn
- os.system('rst2html.py %s > %s' % (fn, new))
diff --git a/lib/paste/debug/fsdiff.py b/lib/paste/debug/fsdiff.py
@@ -1,409 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Module to find differences over time in a filesystem
-
-Basically this takes a snapshot of a directory, then sees what changes
-were made. The contents of the files are not checked, so you can
-detect that the content was changed, but not what the old version of
-the file was.
-"""
-
-import os
-from fnmatch import fnmatch
-from datetime import datetime
-from paste.util.UserDict24 import IterableUserDict
-import operator
-import re
-
-__all__ = ['Diff', 'Snapshot', 'File', 'Dir', 'report_expected_diffs',
- 'show_diff']
-
-class Diff(object):
-
- """
- Represents the difference between two snapshots
- """
-
- def __init__(self, before, after):
- self.before = before
- self.after = after
- self._calculate()
-
- def _calculate(self):
- before = self.before.data
- after = self.after.data
- self.deleted = {}
- self.updated = {}
- self.created = after.copy()
- for path, f in before.items():
- if path not in after:
- self.deleted[path] = f
- continue
- del self.created[path]
- if f.mtime < after[path].mtime:
- self.updated[path] = after[path]
-
- def __str__(self):
- return self.report()
-
- def report(self, header=True, dates=False):
- s = []
- if header:
- s.append('Difference in %s from %s to %s:' %
- (self.before.base_path,
- self.before.calculated,
- self.after.calculated))
- for name, files, show_size in [
- ('created', self.created, True),
- ('deleted', self.deleted, True),
- ('updated', self.updated, True)]:
- if files:
- s.append('-- %s: -------------------' % name)
- files = files.items()
- files.sort()
- last = ''
- for path, f in files:
- t = ' %s' % _space_prefix(last, path, indent=4,
- include_sep=False)
- last = path
- if show_size and f.size != 'N/A':
- t += ' (%s bytes)' % f.size
- if dates:
- parts = []
- if self.before.get(path):
- parts.append(self.before[path].mtime)
- if self.after.get(path):
- parts.append(self.after[path].mtime)
- t += ' (mtime: %s)' % ('->'.join(map(repr, parts)))
- s.append(t)
- if len(s) == 1:
- s.append(' (no changes)')
- return '\n'.join(s)
-
-class Snapshot(IterableUserDict):
-
- """
- Represents a snapshot of a set of files. Has a dictionary-like
- interface, keyed relative to ``base_path``
- """
-
- def __init__(self, base_path, files=None, ignore_wildcards=(),
- ignore_paths=(), ignore_hidden=True):
- self.base_path = base_path
- self.ignore_wildcards = ignore_wildcards
- self.ignore_hidden = ignore_hidden
- self.ignore_paths = ignore_paths
- self.calculated = None
- self.data = files or {}
- if files is None:
- self.find_files()
-
- ############################################################
- ## File finding
- ############################################################
-
- def find_files(self):
- """
- Find all the files under the base path, and put them in
- ``self.data``
- """
- self._find_traverse('', self.data)
- self.calculated = datetime.now()
-
- 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
- for pat in self.ignore_wildcards:
- if fnmatch(fn, pat):
- 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):
- if path:
- # Don't actually include the base path
- result[path] = Dir(self.base_path, path)
- for fn in os.listdir(full):
- fn = os.path.join(path, fn)
- if self._ignore_file(fn):
- continue
- self._find_traverse(fn, result)
- else:
- result[path] = File(self.base_path, path)
-
- def __repr__(self):
- return '<%s in %r from %r>' % (
- self.__class__.__name__, self.base_path,
- self.calculated or '(no calculation done)')
-
- def compare_expected(self, expected, comparison=operator.eq,
- differ=None, not_found=None,
- include_success=False):
- """
- Compares a dictionary of ``path: content`` to the
- found files. Comparison is done by equality, or the
- ``comparison(actual_content, expected_content)`` function given.
-
- Returns dictionary of differences, keyed by path. Each
- difference is either noted, or the output of
- ``differ(actual_content, expected_content)`` is given.
-
- If a file does not exist and ``not_found`` is given, then
- ``not_found(path)`` is put in.
- """
- result = {}
- for path in expected:
- orig_path = path
- path = path.strip('/')
- if path not in self.data:
- if not_found:
- msg = not_found(path)
- else:
- msg = 'not found'
- result[path] = msg
- continue
- expected_content = expected[orig_path]
- file = self.data[path]
- actual_content = file.bytes
- if not comparison(actual_content, expected_content):
- if differ:
- msg = differ(actual_content, expected_content)
- else:
- if len(actual_content) < len(expected_content):
- msg = 'differ (%i bytes smaller)' % (
- len(expected_content) - len(actual_content))
- elif len(actual_content) > len(expected_content):
- msg = 'differ (%i bytes larger)' % (
- len(actual_content) - len(expected_content))
- else:
- msg = 'diff (same size)'
- result[path] = msg
- elif include_success:
- result[path] = 'same!'
- return result
-
- def diff_to_now(self):
- return Diff(self, self.clone())
-
- def clone(self):
- return self.__class__(base_path=self.base_path,
- ignore_wildcards=self.ignore_wildcards,
- ignore_paths=self.ignore_paths,
- ignore_hidden=self.ignore_hidden)
-
-class File(object):
-
- """
- Represents a single file found as the result of a command.
-
- Has attributes:
-
- ``path``:
- The path of the file, relative to the ``base_path``
-
- ``full``:
- The full path
-
- ``stat``:
- The results of ``os.stat``. Also ``mtime`` and ``size``
- contain the ``.st_mtime`` and ``st_size`` of the stat.
-
- ``bytes``:
- The contents of the file.
-
- You may use the ``in`` operator with these objects (tested against
- the contents of the file), and the ``.mustcontain()`` method.
- """
-
- file = True
- dir = False
-
- def __init__(self, base_path, path):
- self.base_path = base_path
- self.path = path
- self.full = os.path.join(base_path, path)
- self.stat = os.stat(self.full)
- self.mtime = self.stat.st_mtime
- self.size = self.stat.st_size
- self._bytes = None
-
- def bytes__get(self):
- if self._bytes is None:
- f = open(self.full, 'rb')
- self._bytes = f.read()
- f.close()
- return self._bytes
- bytes = property(bytes__get)
-
- def __contains__(self, s):
- return s in self.bytes
-
- def mustcontain(self, s):
- __tracebackhide__ = True
- bytes = self.bytes
- if s not in bytes:
- print 'Could not find %r in:' % s
- print bytes
- assert s in bytes
-
- def __repr__(self):
- return '<%s %s:%s>' % (
- self.__class__.__name__,
- self.base_path, self.path)
-
-class Dir(File):
-
- """
- Represents a directory created by a command.
- """
-
- file = False
- dir = True
-
- def __init__(self, base_path, path):
- self.base_path = base_path
- self.path = path
- self.full = os.path.join(base_path, path)
- self.size = 'N/A'
- self.mtime = 'N/A'
-
- def __repr__(self):
- return '<%s %s:%s>' % (
- self.__class__.__name__,
- self.base_path, self.path)
-
- def bytes__get(self):
- raise NotImplementedError(
- "Directory %r doesn't have content" % self)
-
- bytes = property(bytes__get)
-
-
-def _space_prefix(pref, full, sep=None, indent=None, include_sep=True):
- """
- Anything shared by pref and full will be replaced with spaces
- in full, and full returned.
-
- Example::
-
- >>> _space_prefix('/foo/bar', '/foo')
- ' /bar'
- """
- if sep is None:
- sep = os.path.sep
- pref = pref.split(sep)
- full = full.split(sep)
- padding = []
- while pref and full and pref[0] == full[0]:
- if indent is None:
- padding.append(' ' * (len(full[0]) + len(sep)))
- else:
- padding.append(' ' * indent)
- full.pop(0)
- pref.pop(0)
- if padding:
- if include_sep:
- return ''.join(padding) + sep + sep.join(full)
- else:
- return ''.join(padding) + sep.join(full)
- else:
- return sep.join(full)
-
-def report_expected_diffs(diffs, colorize=False):
- """
- Takes the output of compare_expected, and returns a string
- description of the differences.
- """
- if not diffs:
- return 'No differences'
- diffs = diffs.items()
- diffs.sort()
- s = []
- last = ''
- for path, desc in diffs:
- t = _space_prefix(last, path, indent=4, include_sep=False)
- if colorize:
- t = color_line(t, 11)
- last = path
- if len(desc.splitlines()) > 1:
- cur_indent = len(re.search(r'^[ ]*', t).group(0))
- desc = indent(cur_indent+2, desc)
- if colorize:
- t += '\n'
- for line in desc.splitlines():
- if line.strip().startswith('+'):
- line = color_line(line, 10)
- elif line.strip().startswith('-'):
- line = color_line(line, 9)
- else:
- line = color_line(line, 14)
- t += line+'\n'
- else:
- t += '\n' + desc
- else:
- t += ' '+desc
- s.append(t)
- s.append('Files with differences: %s' % len(diffs))
- return '\n'.join(s)
-
-def color_code(foreground=None, background=None):
- """
- 0 black
- 1 red
- 2 green
- 3 yellow
- 4 blue
- 5 magenta (purple)
- 6 cyan
- 7 white (gray)
-
- Add 8 to get high-intensity
- """
- if foreground is None and background is None:
- # Reset
- return '\x1b[0m'
- codes = []
- if foreground is None:
- codes.append('[39m')
- elif foreground > 7:
- codes.append('[1m')
- codes.append('[%im' % (22+foreground))
- else:
- codes.append('[%im' % (30+foreground))
- if background is None:
- codes.append('[49m')
- else:
- codes.append('[%im' % (40+background))
- return '\x1b' + '\x1b'.join(codes)
-
-def color_line(line, foreground=None, background=None):
- match = re.search(r'^(\s*)', line)
- return (match.group(1) + color_code(foreground, background)
- + line[match.end():] + color_code())
-
-def indent(indent, text):
- return '\n'.join(
- [' '*indent + l for l in text.splitlines()])
-
-def show_diff(actual_content, expected_content):
- actual_lines = [l.strip() for l in actual_content.splitlines()
- if l.strip()]
- expected_lines = [l.strip() for l in expected_content.splitlines()
- if l.strip()]
- if len(actual_lines) == len(expected_lines) == 1:
- return '%r not %r' % (actual_lines[0], expected_lines[0])
- if not actual_lines:
- return 'Empty; should have:\n'+expected_content
- import difflib
- return '\n'.join(difflib.ndiff(actual_lines, expected_lines))
diff --git a/lib/paste/debug/prints.py b/lib/paste/debug/prints.py
@@ -1,148 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Middleware that displays everything that is printed inline in
-application pages.
-
-Anything printed during the request will get captured and included on
-the page. It will usually be included as a floating element in the
-top right hand corner of the page. If you want to override this
-you can include a tag in your template where it will be placed::
-
- <pre id="paste-debug-prints"></pre>
-
-You might want to include ``style="white-space: normal"``, as all the
-whitespace will be quoted, and this allows the text to wrap if
-necessary.
-
-"""
-
-from cStringIO import StringIO
-import re
-import cgi
-from paste.util import threadedprint
-from paste import wsgilib
-from paste import response
-import sys
-
-_threadedprint_installed = False
-
-__all__ = ['PrintDebugMiddleware']
-
-class TeeFile(object):
-
- def __init__(self, files):
- self.files = files
-
- def write(self, v):
- if isinstance(v, unicode):
- # WSGI is picky in this case
- v = str(v)
- for file in self.files:
- file.write(v)
-
-class PrintDebugMiddleware(object):
-
- """
- This middleware captures all the printed statements, and inlines
- them in HTML pages, so that you can see all the (debug-intended)
- print statements in the page itself.
-
- There are two keys added to the environment to control this:
- ``environ['paste.printdebug_listeners']`` is a list of functions
- that will be called everytime something is printed.
-
- ``environ['paste.remove_printdebug']`` is a function that, if
- called, will disable printing of output for that request.
-
- If you have ``replace_stdout=True`` then stdout is replaced, not
- captured.
- """
-
- log_template = (
- '<pre style="width: 40%%; border: 2px solid #000; white-space: normal; '
- 'background-color: #ffd; color: #000; float: right;">'
- '<b style="border-bottom: 1px solid #000">Log messages</b><br>'
- '%s</pre>')
-
- def __init__(self, app, global_conf=None, force_content_type=False,
- print_wsgi_errors=True, replace_stdout=False):
- # @@: global_conf should be handled separately and only for
- # the entry point
- self.app = app
- self.force_content_type = force_content_type
- if isinstance(print_wsgi_errors, basestring):
- from paste.deploy.converters import asbool
- print_wsgi_errors = asbool(print_wsgi_errors)
- self.print_wsgi_errors = print_wsgi_errors
- self.replace_stdout = replace_stdout
- self._threaded_print_stdout = None
-
- def __call__(self, environ, start_response):
- global _threadedprint_installed
- if environ.get('paste.testing'):
- # In a testing environment this interception isn't
- # useful:
- return self.app(environ, start_response)
- if (not _threadedprint_installed
- or self._threaded_print_stdout is not sys.stdout):
- # @@: Not strictly threadsafe
- _threadedprint_installed = True
- threadedprint.install(leave_stdout=not self.replace_stdout)
- self._threaded_print_stdout = sys.stdout
- removed = []
- def remove_printdebug():
- removed.append(None)
- environ['paste.remove_printdebug'] = remove_printdebug
- logged = StringIO()
- listeners = [logged]
- environ['paste.printdebug_listeners'] = listeners
- if self.print_wsgi_errors:
- listeners.append(environ['wsgi.errors'])
- replacement_stdout = TeeFile(listeners)
- threadedprint.register(replacement_stdout)
- try:
- status, headers, body = wsgilib.intercept_output(
- environ, self.app)
- if status is None:
- # Some error occurred
- status = '500 Server Error'
- headers = [('Content-type', 'text/html')]
- start_response(status, headers)
- if not body:
- body = 'An error occurred'
- content_type = response.header_value(headers, 'content-type')
- if (removed or
- (not self.force_content_type and
- (not content_type
- or not content_type.startswith('text/html')))):
- if replacement_stdout == logged:
- # Then the prints will be lost, unless...
- environ['wsgi.errors'].write(logged.getvalue())
- start_response(status, headers)
- return [body]
- response.remove_header(headers, 'content-length')
- body = self.add_log(body, logged.getvalue())
- start_response(status, headers)
- return [body]
- finally:
- threadedprint.deregister()
-
- _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
- text = cgi.escape(log)
- text = text.replace('\n', '<br>')
- text = text.replace(' ', ' ')
- match = self._explicit_re.search(html)
- if not match:
- text = self.log_template % text
- match = self._body_re.search(html)
- if not match:
- return text + html
- else:
- return html[:match.end()] + text + html[match.end():]
diff --git a/lib/paste/debug/profile.py b/lib/paste/debug/profile.py
@@ -1,227 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Middleware that profiles the request and displays profiling
-information at the bottom of each page.
-"""
-
-
-import sys
-import os
-import hotshot
-import hotshot.stats
-import threading
-import cgi
-import time
-from cStringIO import StringIO
-from paste import response
-
-__all__ = ['ProfileMiddleware', 'profile_decorator']
-
-class ProfileMiddleware(object):
-
- """
- Middleware that profiles all requests.
-
- All HTML pages will have profiling information appended to them.
- The data is isolated to that single request, and does not include
- data from previous requests.
-
- This uses the ``hotshot`` module, which affects performance of the
- application. It also runs in a single-threaded mode, so it is
- only usable in development environments.
- """
-
- style = ('clear: both; background-color: #ff9; color: #000; '
- 'border: 2px solid #000; padding: 5px;')
-
- def __init__(self, app, global_conf=None,
- log_filename='profile.log.tmp',
- limit=40):
- self.app = app
- self.lock = threading.Lock()
- self.log_filename = log_filename
- self.limit = limit
-
- def __call__(self, environ, start_response):
- catch_response = []
- body = []
- def replace_start_response(status, headers, exc_info=None):
- catch_response.extend([status, headers])
- start_response(status, headers, exc_info)
- return body.append
- def run_app():
- app_iter = self.app(environ, replace_start_response)
- try:
- body.extend(app_iter)
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- self.lock.acquire()
- try:
- prof = hotshot.Profile(self.log_filename)
- prof.addinfo('URL', environ.get('PATH_INFO', ''))
- try:
- prof.runcall(run_app)
- finally:
- prof.close()
- body = ''.join(body)
- headers = catch_response[1]
- content_type = response.header_value(headers, 'content-type')
- if content_type is None or not content_type.startswith('text/html'):
- # We can't add info to non-HTML output
- return [body]
- stats = hotshot.stats.load(self.log_filename)
- stats.strip_dirs()
- stats.sort_stats('time', 'calls')
- output = capture_output(stats.print_stats, self.limit)
- output_callers = capture_output(
- stats.print_callers, self.limit)
- body += '<pre style="%s">%s\n%s</pre>' % (
- self.style, cgi.escape(output), cgi.escape(output_callers))
- return [body]
- finally:
- self.lock.release()
-
-def capture_output(func, *args, **kw):
- # Not threadsafe! (that's okay when ProfileMiddleware uses it,
- # though, since it synchronizes itself.)
- out = StringIO()
- old_stdout = sys.stdout
- sys.stdout = out
- try:
- func(*args, **kw)
- finally:
- sys.stdout = old_stdout
- return out.getvalue()
-
-def profile_decorator(**options):
-
- """
- Profile a single function call.
-
- Used around a function, like::
-
- @profile_decorator(options...)
- def ...
-
- All calls to the function will be profiled. The options are
- all keywords, and are:
-
- log_file:
- The filename to log to (or ``'stdout'`` or ``'stderr'``).
- Default: stderr.
- display_limit:
- Only show the top N items, default: 20.
- sort_stats:
- A list of string-attributes to sort on. Default
- ``('time', 'calls')``.
- strip_dirs:
- Strip directories/module names from files? Default True.
- add_info:
- If given, this info will be added to the report (for your
- own tracking). Default: none.
- log_filename:
- The temporary filename to log profiling data to. Default;
- ``./profile_data.log.tmp``
- no_profile:
- If true, then don't actually profile anything. Useful for
- conditional profiling.
- """
-
- if options.get('no_profile'):
- def decorator(func):
- return func
- return decorator
- def decorator(func):
- def replacement(*args, **kw):
- return DecoratedProfile(func, **options)(*args, **kw)
- return replacement
- return decorator
-
-class DecoratedProfile(object):
-
- lock = threading.Lock()
-
- def __init__(self, func, **options):
- self.func = func
- self.options = options
-
- def __call__(self, *args, **kw):
- self.lock.acquire()
- try:
- return self.profile(self.func, *args, **kw)
- finally:
- self.lock.release()
-
- def profile(self, func, *args, **kw):
- ops = self.options
- prof_filename = ops.get('log_filename', 'profile_data.log.tmp')
- prof = hotshot.Profile(prof_filename)
- prof.addinfo('Function Call',
- self.format_function(func, *args, **kw))
- if ops.get('add_info'):
- prof.addinfo('Extra info', ops['add_info'])
- exc_info = None
- try:
- start_time = time.time()
- try:
- result = prof.runcall(func, *args, **kw)
- except:
- exc_info = sys.exc_info()
- end_time = time.time()
- finally:
- prof.close()
- stats = hotshot.stats.load(prof_filename)
- os.unlink(prof_filename)
- if ops.get('strip_dirs', True):
- stats.strip_dirs()
- stats.sort_stats(*ops.get('sort_stats', ('time', 'calls')))
- display_limit = ops.get('display_limit', 20)
- output = capture_output(stats.print_stats, display_limit)
- output_callers = capture_output(
- stats.print_callers, display_limit)
- output_file = ops.get('log_file')
- if output_file in (None, 'stderr'):
- f = sys.stderr
- elif output_file in ('-', 'stdout'):
- f = sys.stdout
- else:
- f = open(output_file, 'a')
- f.write('\n%s\n' % ('-'*60))
- f.write('Date: %s\n' % time.strftime('%c'))
- f.write('Function call: %s\n'
- % self.format_function(func, *args, **kw))
- f.write('Wall time: %0.2f seconds\n'
- % (end_time - start_time))
- f.write(output)
- f.write(output_callers)
- if output_file not in (None, '-', 'stdout', 'stderr'):
- f.close()
- if exc_info:
- # We captured an exception earlier, now we re-raise it
- raise 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',
- limit=40):
- """
- Wrap the application in a component that will profile each
- request. The profiling data is then appended to the output
- of each page.
-
- Note that this serializes all requests (i.e., removing
- concurrency). Therefore never use this in production.
- """
- limit = int(limit)
- return ProfileMiddleware(
- app, log_filename=log_filename, limit=limit)
diff --git a/lib/paste/debug/testserver.py b/lib/paste/debug/testserver.py
@@ -1,93 +0,0 @@
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-WSGI Test Server
-
-This builds upon paste.util.baseserver to customize it for regressions
-where using raw_interactive won't do.
-
-
-"""
-import time
-from paste.httpserver import *
-
-class WSGIRegressionServer(WSGIServer):
- """
- A threaded WSGIServer for use in regression testing. To use this
- module, call serve(application, regression=True), and then call
- server.accept() to let it handle one request. When finished, use
- server.stop() to shutdown the server. Note that all pending requests
- are processed before the server shuts down.
- """
- defaulttimeout = 10
- def __init__ (self, *args, **kwargs):
- WSGIServer.__init__(self, *args, **kwargs)
- self.stopping = []
- self.pending = []
- self.timeout = self.defaulttimeout
- # this is a local connection, be quick
- self.socket.settimeout(2)
- def serve_forever(self):
- from threading import Thread
- thread = Thread(target=self.serve_pending)
- thread.start()
- def reset_expires(self):
- if self.timeout:
- self.expires = time.time() + self.timeout
- def close_request(self, *args, **kwargs):
- WSGIServer.close_request(self, *args, **kwargs)
- self.pending.pop()
- self.reset_expires()
- def serve_pending(self):
- self.reset_expires()
- while not self.stopping or self.pending:
- now = time.time()
- if now > self.expires and self.timeout:
- # note regression test doesn't handle exceptions in
- # threads very well; so we just print and exit
- print "\nWARNING: WSGIRegressionServer timeout exceeded\n"
- break
- if self.pending:
- self.handle_request()
- time.sleep(.1)
- def stop(self):
- """ stop the server (called from tester's thread) """
- self.stopping.append(True)
- def accept(self, count = 1):
- """ accept another request (called from tester's thread) """
- assert not self.stopping
- [self.pending.append(True) for x in range(count)]
-
-def serve(application, host=None, port=None, handler=None):
- server = WSGIRegressionServer(application, host, port, handler)
- print "serving on %s:%s" % server.server_address
- server.serve_forever()
- return server
-
-if __name__ == '__main__':
- import urllib
- from paste.wsgilib import dump_environ
- server = serve(dump_environ)
- baseuri = ("http://%s:%s" % server.server_address)
-
- 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
- # may not respond in a resonable time period
- import socket
- socket.setdefaulttimeout(5)
- # build a uri, fetch and return
- return urllib.urlopen(baseuri + path).read()
-
- assert "PATH_INFO: /foo" in fetch("/foo")
- assert "PATH_INFO: /womble" in fetch("/womble")
-
- # ok, let's make one more final request...
- server.accept(1)
- # and then schedule a stop()
- server.stop()
- # and then... fetch it...
- urllib.urlopen(baseuri)
diff --git a/lib/paste/debug/watchthreads.py b/lib/paste/debug/watchthreads.py
@@ -1,347 +0,0 @@
-"""
-Watches the key ``paste.httpserver.thread_pool`` to see how many
-threads there are and report on any wedged threads.
-"""
-import sys
-import cgi
-import time
-import traceback
-from cStringIO import StringIO
-from thread import get_ident
-from paste import httpexceptions
-from paste.request import construct_url, parse_formvars
-from paste.util.template import HTMLTemplate, bunch
-
-page_template = HTMLTemplate('''
-<html>
- <head>
- <style type="text/css">
- body {
- font-family: sans-serif;
- }
- table.environ tr td {
- border-bottom: #bbb 1px solid;
- }
- table.environ tr td.bottom {
- border-bottom: none;
- }
- table.thread {
- border: 1px solid #000;
- margin-bottom: 1em;
- }
- table.thread tr td {
- border-bottom: #999 1px solid;
- padding-right: 1em;
- }
- table.thread tr td.bottom {
- border-bottom: none;
- }
- table.thread tr.this_thread td {
- background-color: #006;
- color: #fff;
- }
- a.button {
- background-color: #ddd;
- border: #aaa outset 2px;
- text-decoration: none;
- margin-top: 10px;
- font-size: 80%;
- color: #000;
- }
- a.button:hover {
- background-color: #eee;
- border: #bbb outset 2px;
- }
- a.button:active {
- border: #bbb inset 2px;
- }
- </style>
- <title>{{title}}</title>
- </head>
- <body>
- <h1>{{title}}</h1>
- {{if kill_thread_id}}
- <div style="background-color: #060; color: #fff;
- border: 2px solid #000;">
- Thread {{kill_thread_id}} killed
- </div>
- {{endif}}
- <div>Pool size: {{nworkers}}
- {{if actual_workers > nworkers}}
- + {{actual_workers-nworkers}} extra
- {{endif}}
- ({{nworkers_used}} used including current request)<br>
- idle: {{len(track_threads["idle"])}},
- busy: {{len(track_threads["busy"])}},
- hung: {{len(track_threads["hung"])}},
- dying: {{len(track_threads["dying"])}},
- zombie: {{len(track_threads["zombie"])}}</div>
-
-{{for thread in threads}}
-
-<table class="thread">
- <tr {{if thread.thread_id == this_thread_id}}class="this_thread"{{endif}}>
- <td>
- <b>Thread</b>
- {{if thread.thread_id == this_thread_id}}
- (<i>this</i> request)
- {{endif}}</td>
- <td>
- <b>{{thread.thread_id}}
- {{if allow_kill}}
- <form action="{{script_name}}/kill" method="POST"
- style="display: inline">
- <input type="hidden" name="thread_id" value="{{thread.thread_id}}">
- <input type="submit" value="kill">
- </form>
- {{endif}}
- </b>
- </td>
- </tr>
- <tr>
- <td>Time processing request</td>
- <td>{{thread.time_html|html}}</td>
- </tr>
- <tr>
- <td>URI</td>
- <td>{{if thread.uri == 'unknown'}}
- unknown
- {{else}}<a href="{{thread.uri}}">{{thread.uri_short}}</a>
- {{endif}}
- </td>
- <tr>
- <td colspan="2" class="bottom">
- <a href="#" class="button" style="width: 9em; display: block"
- onclick="
- var el = document.getElementById('environ-{{thread.thread_id}}');
- if (el.style.display) {
- el.style.display = '';
- this.innerHTML = \'▾ Hide environ\';
- } else {
- el.style.display = 'none';
- this.innerHTML = \'▸ Show environ\';
- }
- return false
- ">▸ Show environ</a>
-
- <div id="environ-{{thread.thread_id}}" style="display: none">
- {{if thread.environ:}}
- <table class="environ">
- {{for loop, item in looper(sorted(thread.environ.items()))}}
- {{py:key, value=item}}
- <tr>
- <td {{if loop.last}}class="bottom"{{endif}}>{{key}}</td>
- <td {{if loop.last}}class="bottom"{{endif}}>{{value}}</td>
- </tr>
- {{endfor}}
- </table>
- {{else}}
- Thread is in process of starting
- {{endif}}
- </div>
-
- {{if thread.traceback}}
- <a href="#" class="button" style="width: 9em; display: block"
- onclick="
- var el = document.getElementById('traceback-{{thread.thread_id}}');
- if (el.style.display) {
- el.style.display = '';
- this.innerHTML = \'▾ Hide traceback\';
- } else {
- el.style.display = 'none';
- this.innerHTML = \'▸ Show traceback\';
- }
- return false
- ">▸ Show traceback</a>
-
- <div id="traceback-{{thread.thread_id}}" style="display: none">
- <pre class="traceback">{{thread.traceback}}</pre>
- </div>
- {{endif}}
-
- </td>
- </tr>
-</table>
-
-{{endfor}}
-
- </body>
-</html>
-''', name='watchthreads.page_template')
-
-class WatchThreads(object):
-
- """
- Application that watches the threads in ``paste.httpserver``,
- showing the length each thread has been working on a request.
-
- If allow_kill is true, then you can kill errant threads through
- this application.
-
- This application can expose private information (specifically in
- the environment, like cookies), so it should be protected.
- """
-
- def __init__(self, allow_kill=False):
- self.allow_kill = allow_kill
-
- def __call__(self, environ, start_response):
- if 'paste.httpserver.thread_pool' not in environ:
- start_response('403 Forbidden', [('Content-type', 'text/plain')])
- return ['You must use the threaded Paste HTTP server to use this application']
- if environ.get('PATH_INFO') == '/kill':
- return self.kill(environ, start_response)
- else:
- return self.show(environ, start_response)
-
- def show(self, environ, start_response):
- start_response('200 OK', [('Content-type', 'text/html')])
- form = parse_formvars(environ)
- if form.get('kill'):
- kill_thread_id = form['kill']
- else:
- kill_thread_id = None
- thread_pool = environ['paste.httpserver.thread_pool']
- nworkers = thread_pool.nworkers
- now = time.time()
-
-
- workers = thread_pool.worker_tracker.items()
- workers.sort(key=lambda v: v[1][0])
- threads = []
- for thread_id, (time_started, worker_environ) in workers:
- thread = bunch()
- threads.append(thread)
- if worker_environ:
- thread.uri = construct_url(worker_environ)
- else:
- thread.uri = 'unknown'
- thread.thread_id = thread_id
- thread.time_html = format_time(now-time_started)
- 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,
- actual_workers=len(thread_pool.workers),
- nworkers_used=len(workers),
- script_name=environ['SCRIPT_NAME'],
- kill_thread_id=kill_thread_id,
- allow_kill=self.allow_kill,
- threads=threads,
- this_thread_id=get_ident(),
- track_threads=thread_pool.track_threads())
-
- return [page]
-
- def kill(self, environ, start_response):
- if not self.allow_kill:
- exc = httpexceptions.HTTPForbidden(
- 'Killing threads has not been enabled. Shame on you '
- 'for trying!')
- return exc(environ, start_response)
- vars = parse_formvars(environ)
- thread_id = int(vars['thread_id'])
- thread_pool = environ['paste.httpserver.thread_pool']
- if thread_id not in thread_pool.worker_tracker:
- exc = httpexceptions.PreconditionFailed(
- 'You tried to kill thread %s, but it is not working on '
- 'any requests' % thread_id)
- return exc(environ, start_response)
- thread_pool.kill_worker(thread_id)
- script_name = environ['SCRIPT_NAME'] or '/'
- 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
- can't get a traceback.
- """
- if not hasattr(sys, '_current_frames'):
- # Only 2.5 has support for this, with this special function
- return None
- frames = sys._current_frames()
- if not thread_id in frames:
- return None
- frame = frames[thread_id]
- out = StringIO()
- traceback.print_stack(frame, file=out)
- return out.getvalue()
-
-hide_keys = ['paste.httpserver.thread_pool']
-
-def format_environ(environ):
- if environ is None:
- return environ_template.substitute(
- key='---',
- value='No environment registered for this thread yet')
- environ_rows = []
- for key, value in sorted(environ.items()):
- if key in hide_keys:
- continue
- try:
- if key.upper() != key:
- value = repr(value)
- environ_rows.append(
- environ_template.substitute(
- key=cgi.escape(str(key)),
- value=cgi.escape(str(value))))
- except Exception, e:
- environ_rows.append(
- environ_template.substitute(
- 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
- time_string = '%i:%02i:%02i' % (int(time_length/60/60),
- int(time_length/60) % 60,
- time_length % 60)
- elif time_length >= 120:
- time_string = '%i:%02i' % (int(time_length/60),
- time_length % 60)
- elif time_length > 60:
- time_string = '%i sec' % time_length
- elif time_length > 1:
- time_string = '%0.1f sec' % time_length
- else:
- time_string = '%0.2f sec' % time_length
- if time_length < 5:
- return time_string
- elif time_length < 120:
- return '<span style="color: #900">%s</span>' % time_string
- else:
- return '<span style="background-color: #600; color: #fff">%s</span>' % time_string
-
-def shorten(s):
- if len(s) > 60:
- return s[:40]+'...'+s[-10:]
- else:
- return s
-
-def make_watch_threads(global_conf, allow_kill=False):
- from paste.deploy.converters import asbool
- return WatchThreads(allow_kill=asbool(allow_kill))
-make_watch_threads.__doc__ = WatchThreads.__doc__
-
-def make_bad_app(global_conf, pause=0):
- pause = int(pause)
- def bad_app(environ, start_response):
- import thread
- if pause:
- time.sleep(pause)
- else:
- count = 0
- while 1:
- print "I'm alive %s (%s)" % (count, thread.get_ident())
- time.sleep(10)
- count += 1
- start_response('200 OK', [('content-type', 'text/plain')])
- return ['OK, paused %s seconds' % pause]
- return bad_app
diff --git a/lib/paste/debug/wdg_validate.py b/lib/paste/debug/wdg_validate.py
@@ -1,117 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Middleware that tests the validity of all generated HTML using the
-`WDG HTML Validator <http://www.htmlhelp.com/tools/validator/>`_
-"""
-
-from cStringIO import StringIO
-try:
- import subprocess
-except ImportError:
- from paste.util import subprocess24 as subprocess
-from paste import wsgilib
-import re
-import cgi
-
-__all__ = ['WDGValidateMiddleware']
-
-class WDGValidateMiddleware(object):
-
- """
- Middleware that checks HTML and appends messages about the validity of
- the HTML. Uses: http://www.htmlhelp.com/tools/validator/ -- interacts
- with the command line client. Use the configuration ``wdg_path`` to
- override the path (default: looks for ``validate`` in $PATH).
-
- To install, in your web context's __init__.py::
-
- def urlparser_wrap(environ, start_response, app):
- return wdg_validate.WDGValidateMiddleware(app)(
- environ, start_response)
-
- Or in your configuration::
-
- middleware.append('paste.wdg_validate.WDGValidateMiddleware')
- """
-
- _end_body_regex = re.compile(r'</body>', re.I)
-
- def __init__(self, app, global_conf=None, wdg_path='validate'):
- self.app = app
- self.wdg_path = wdg_path
-
- def __call__(self, environ, start_response):
- output = StringIO()
- response = []
-
- def writer_start_response(status, headers, exc_info=None):
- response.extend((status, headers))
- start_response(status, headers, exc_info)
- return output.write
-
- app_iter = self.app(environ, writer_start_response)
- try:
- for s in app_iter:
- output.write(s)
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- page = output.getvalue()
- status, headers = response
- v = wsgilib.header_value(headers, 'content-type') or ''
- if (not v.startswith('text/html')
- and not v.startswith('text/xhtml')
- and not v.startswith('application/xhtml')):
- # Can't validate
- # @@: Should validate CSS too... but using what?
- return [page]
- ops = []
- if v.startswith('text/xhtml+xml'):
- ops.append('--xml')
- # @@: Should capture encoding too
- html_errors = self.call_wdg_validate(
- self.wdg_path, ops, page)
- if not html_errors:
- return [page]
- return self.add_error(page, html_errors)
-
- def call_wdg_validate(self, wdg_path, ops, page):
- if subprocess is None:
- raise ValueError(
- "This middleware requires the subprocess module from "
- "Python 2.4")
- proc = subprocess.Popen([wdg_path] + ops,
- shell=False,
- close_fds=True,
- stdout=subprocess.PIPE,
- stdin=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- stdout = proc.communicate(page)[0]
- proc.wait()
- return stdout
-
- def add_error(self, html_page, html_errors):
- add_text = ('<pre style="background-color: #ffd; color: #600; '
- 'border: 1px solid #000;">%s</pre>'
- % cgi.escape(html_errors))
- match = self._end_body_regex.search(html_page)
- if match:
- return [html_page[:match.start()]
- + add_text
- + html_page[match.end():]]
- else:
- return [html_page + add_text]
-
-def make_wdg_validate_middleware(
- app, global_conf, wdg_path='validate'):
- """
- Wraps the application in the WDG validator from
- http://www.htmlhelp.com/tools/validator/
-
- Validation errors are appended to the text of each page.
- You can configure this by giving the path to the validate
- executable (by default picked up from $PATH)
- """
- return WDGValidateMiddleware(
- app, global_conf, wdg_path=wdg_path)
diff --git a/lib/paste/errordocument.py b/lib/paste/errordocument.py
@@ -1,372 +0,0 @@
-# (c) 2005-2006 James Gardner <james@pythonweb.org>
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-"""
-Middleware to display error documents for certain status codes
-
-The middleware in this module can be used to intercept responses with
-specified status codes and internally forward the request to an appropriate
-URL where the content can be displayed to the user as an error document.
-"""
-
-import warnings
-from urlparse import urlparse
-from paste.recursive import ForwardRequestException, RecursiveMiddleware
-from paste.util import converters
-from paste.response import replace_header
-
-def forward(app, codes):
- """
- Intercepts a response with a particular status code and returns the
- content from a specified URL instead.
-
- The arguments are:
-
- ``app``
- The WSGI application or middleware chain.
-
- ``codes``
- A dictionary of integer status codes and the URL to be displayed
- if the response uses that code.
-
- For example, you might want to create a static file to display a
- "File Not Found" message at the URL ``/error404.html`` and then use
- ``forward`` middleware to catch all 404 status codes and display the page
- you created. In this example ``app`` is your exisiting WSGI
- applicaiton::
-
- from paste.errordocument import forward
- app = forward(app, codes={404:'/error404.html'})
-
- """
- for code in codes:
- if not isinstance(code, int):
- raise TypeError('All status codes should be type int. '
- '%s is not valid'%repr(code))
-
- def error_codes_mapper(code, message, environ, global_conf, codes):
- if codes.has_key(code):
- return codes[code]
- else:
- return None
-
- #return _StatusBasedRedirect(app, error_codes_mapper, codes=codes)
- return RecursiveMiddleware(
- StatusBasedForward(
- app,
- error_codes_mapper,
- codes=codes,
- )
- )
-
-class StatusKeeper(object):
- def __init__(self, app, status, url, headers):
- self.app = app
- self.status = status
- self.url = url
- self.headers = headers
-
- def __call__(self, environ, start_response):
- def keep_status_start_response(status, headers, exc_info=None):
- for header, value in headers:
- if header.lower() == 'set-cookie':
- self.headers.append((header, value))
- else:
- replace_header(self.headers, header, value)
- return start_response(self.status, self.headers, exc_info)
- parts = self.url.split('?')
- environ['PATH_INFO'] = parts[0]
- if len(parts) > 1:
- environ['QUERY_STRING'] = parts[1]
- else:
- environ['QUERY_STRING'] = ''
- #raise Exception(self.url, self.status)
- return self.app(environ, keep_status_start_response)
-
-class StatusBasedForward(object):
- """
- Middleware that lets you test a response against a custom mapper object to
- programatically determine whether to internally forward to another URL and
- if so, which URL to forward to.
-
- If you don't need the full power of this middleware you might choose to use
- the simpler ``forward`` middleware instead.
-
- The arguments are:
-
- ``app``
- The WSGI application or middleware chain.
-
- ``mapper``
- A callable that takes a status code as the
- first parameter, a message as the second, and accepts optional environ,
- global_conf and named argments afterwards. It should return a
- URL to forward to or ``None`` if the code is not to be intercepted.
-
- ``global_conf``
- Optional default configuration from your config file. If ``debug`` is
- set to ``true`` a message will be written to ``wsgi.errors`` on each
- internal forward stating the URL forwarded to.
-
- ``**params``
- Optional, any other configuration and extra arguments you wish to
- pass which will in turn be passed back to the custom mapper object.
-
- Here is an example where a ``404 File Not Found`` status response would be
- redirected to the URL ``/error?code=404&message=File%20Not%20Found``. This
- could be useful for passing the status code and message into another
- application to display an error document:
-
- .. code-block:: python
-
- from paste.errordocument import StatusBasedForward
- from paste.recursive import RecursiveMiddleware
- from urllib import urlencode
-
- def error_mapper(code, message, environ, global_conf, kw)
- if code in [404, 500]:
- params = urlencode({'message':message, 'code':code})
- url = '/error?'%(params)
- return url
- else:
- return None
-
- app = RecursiveMiddleware(
- StatusBasedForward(app, mapper=error_mapper),
- )
-
- """
-
- def __init__(self, app, mapper, global_conf=None, **params):
- if global_conf is None:
- global_conf = {}
- # @@: global_conf shouldn't really come in here, only in a
- # separate make_status_based_forward function
- if global_conf:
- self.debug = converters.asbool(global_conf.get('debug', False))
- else:
- self.debug = False
- self.application = app
- self.mapper = mapper
- self.global_conf = global_conf
- self.params = params
-
- def __call__(self, environ, start_response):
- url = []
-
- def change_response(status, headers, exc_info=None):
- status_code = status.split(' ')
- try:
- code = int(status_code[0])
- except (ValueError, TypeError):
- raise Exception(
- 'StatusBasedForward middleware '
- 'received an invalid status code %s'%repr(status_code[0])
- )
- message = ' '.join(status_code[1:])
- new_url = self.mapper(
- code,
- message,
- environ,
- self.global_conf,
- **self.params
- )
- if not (new_url == None or isinstance(new_url, str)):
- raise TypeError(
- 'Expected the url to internally '
- 'redirect to in the StatusBasedForward mapper'
- 'to be a string or None, not %s'%repr(new_url)
- )
- if new_url:
- url.append([new_url, status, headers])
- else:
- return start_response(status, headers, exc_info)
-
- app_iter = self.application(environ, change_response)
- if url:
- if hasattr(app_iter, 'close'):
- app_iter.close()
-
- def factory(app):
- return StatusKeeper(app, status=url[0][1], url=url[0][0],
- headers=url[0][2])
- raise ForwardRequestException(factory=factory)
- else:
- return app_iter
-
-def make_errordocument(app, global_conf, **kw):
- """
- Paste Deploy entry point to create a error document wrapper.
-
- Use like::
-
- [filter-app:main]
- use = egg:Paste#errordocument
- next = real-app
- 500 = /lib/msg/500.html
- 404 = /lib/msg/404.html
- """
- map = {}
- for status, redir_loc in kw.items():
- try:
- status = int(status)
- except ValueError:
- raise ValueError('Bad status code: %r' % status)
- map[status] = redir_loc
- forwarder = forward(app, map)
- return forwarder
-
-__pudge_all__ = [
- 'forward',
- 'make_errordocument',
- 'empty_error',
- 'make_empty_error',
- 'StatusBasedForward',
-]
-
-
-###############################################################################
-## Deprecated
-###############################################################################
-
-def custom_forward(app, mapper, global_conf=None, **kw):
- """
- Deprectated; use StatusBasedForward instead.
- """
- warnings.warn(
- "errordocuments.custom_forward has been deprecated; please "
- "use errordocuments.StatusBasedForward",
- DeprecationWarning, 2)
- if global_conf is None:
- global_conf = {}
- return _StatusBasedRedirect(app, mapper, global_conf, **kw)
-
-class _StatusBasedRedirect(object):
- """
- Deprectated; use StatusBasedForward instead.
- """
- def __init__(self, app, mapper, global_conf=None, **kw):
-
- warnings.warn(
- "errordocuments._StatusBasedRedirect has been deprecated; please "
- "use errordocuments.StatusBasedForward",
- DeprecationWarning, 2)
-
- if global_conf is None:
- global_conf = {}
- self.application = app
- self.mapper = mapper
- self.global_conf = global_conf
- self.kw = kw
- self.fallback_template = """
- <html>
- <head>
- <title>Error %(code)s</title>
- </html>
- <body>
- <h1>Error %(code)s</h1>
- <p>%(message)s</p>
- <hr>
- <p>
- Additionally an error occurred trying to produce an
- error document. A description of the error was logged
- to <tt>wsgi.errors</tt>.
- </p>
- </body>
- </html>
- """
-
- def __call__(self, environ, start_response):
- url = []
- code_message = []
- try:
- def change_response(status, headers, exc_info=None):
- new_url = None
- parts = status.split(' ')
- try:
- code = int(parts[0])
- except ValueError, TypeError:
- raise Exception(
- '_StatusBasedRedirect middleware '
- 'received an invalid status code %s'%repr(parts[0])
- )
- message = ' '.join(parts[1:])
- new_url = self.mapper(
- code,
- message,
- environ,
- self.global_conf,
- self.kw
- )
- if not (new_url == None or isinstance(new_url, str)):
- raise TypeError(
- 'Expected the url to internally '
- 'redirect to in the _StatusBasedRedirect error_mapper'
- 'to be a string or None, not %s'%repr(new_url)
- )
- if new_url:
- url.append(new_url)
- code_message.append([code, message])
- return start_response(status, headers, exc_info)
- app_iter = self.application(environ, change_response)
- except:
- try:
- import sys
- error = str(sys.exc_info()[1])
- except:
- error = ''
- try:
- code, message = code_message[0]
- except:
- code, message = ['', '']
- environ['wsgi.errors'].write(
- 'Error occurred in _StatusBasedRedirect '
- 'intercepting the response: '+str(error)
- )
- return [self.fallback_template
- % {'message': message, 'code': code}]
- else:
- if url:
- url_ = url[0]
- new_environ = {}
- for k, v in environ.items():
- if k != 'QUERY_STRING':
- new_environ['QUERY_STRING'] = urlparse(url_)[4]
- else:
- new_environ[k] = v
- class InvalidForward(Exception):
- pass
- def eat_start_response(status, headers, exc_info=None):
- """
- We don't want start_response to do anything since it
- has already been called
- """
- if status[:3] != '200':
- raise InvalidForward(
- "The URL %s to internally forward "
- "to in order to create an error document did not "
- "return a '200' status code." % url_
- )
- forward = environ['paste.recursive.forward']
- old_start_response = forward.start_response
- forward.start_response = eat_start_response
- try:
- app_iter = forward(url_, new_environ)
- except InvalidForward, e:
- code, message = code_message[0]
- environ['wsgi.errors'].write(
- 'Error occurred in '
- '_StatusBasedRedirect redirecting '
- 'to new URL: '+str(url[0])
- )
- return [
- self.fallback_template%{
- 'message':message,
- 'code':code,
- }
- ]
- else:
- forward.start_response = old_start_response
- return app_iter
- else:
- return app_iter
diff --git a/lib/paste/evalexception/__init__.py b/lib/paste/evalexception/__init__.py
@@ -1,7 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-An exception handler for interactive debugging
-"""
-from paste.evalexception.middleware import EvalException
-
diff --git a/lib/paste/evalexception/evalcontext.py b/lib/paste/evalexception/evalcontext.py
@@ -1,68 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-from cStringIO import StringIO
-import traceback
-import threading
-import pdb
-import sys
-
-exec_lock = threading.Lock()
-
-class EvalContext(object):
-
- """
- Class that represents a interactive interface. It has its own
- namespace. Use eval_context.exec_expr(expr) to run commands; the
- output of those commands is returned, as are print statements.
-
- This is essentially what doctest does, and is taken directly from
- doctest.
- """
-
- def __init__(self, namespace, globs):
- self.namespace = namespace
- self.globs = globs
-
- def exec_expr(self, s):
- out = StringIO()
- exec_lock.acquire()
- save_stdout = sys.stdout
- try:
- debugger = _OutputRedirectingPdb(save_stdout)
- debugger.reset()
- pdb.set_trace = debugger.set_trace
- sys.stdout = out
- try:
- code = compile(s, '<web>', "single", 0, 1)
- exec code in self.namespace, self.globs
- debugger.set_continue()
- except KeyboardInterrupt:
- raise
- except:
- traceback.print_exc(file=out)
- debugger.set_continue()
- finally:
- sys.stdout = save_stdout
- exec_lock.release()
- return out.getvalue()
-
-# From doctest
-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
diff --git a/lib/paste/evalexception/media/debug.js b/lib/paste/evalexception/media/debug.js
@@ -1,161 +0,0 @@
-function showFrame(anchor) {
- var tbid = anchor.getAttribute('tbid');
- var expanded = anchor.expanded;
- if (expanded) {
- MochiKit.DOM.hideElement(anchor.expandedElement);
- anchor.expanded = false;
- _swapImage(anchor);
- return false;
- }
- anchor.expanded = true;
- if (anchor.expandedElement) {
- MochiKit.DOM.showElement(anchor.expandedElement);
- _swapImage(anchor);
- $('debug_input_'+tbid).focus();
- return false;
- }
- var url = debug_base
- + '/show_frame?tbid=' + tbid
- + '&debugcount=' + debug_count;
- var d = MochiKit.Async.doSimpleXMLHttpRequest(url);
- d.addCallbacks(function (data) {
- var el = MochiKit.DOM.DIV({});
- anchor.parentNode.insertBefore(el, anchor.nextSibling);
- el.innerHTML = data.responseText;
- anchor.expandedElement = el;
- _swapImage(anchor);
- $('debug_input_'+tbid).focus();
- }, function (error) {
- showError(error.req.responseText);
- });
- return false;
-}
-
-function _swapImage(anchor) {
- var el = anchor.getElementsByTagName('IMG')[0];
- if (anchor.expanded) {
- var img = 'minus.jpg';
- } else {
- var img = 'plus.jpg';
- }
- el.src = debug_base + '/media/' + img;
-}
-
-function submitInput(button, tbid) {
- var input = $(button.getAttribute('input-from'));
- var output = $(button.getAttribute('output-to'));
- var url = debug_base
- + '/exec_input';
- var history = input.form.history;
- input.historyPosition = 0;
- if (! history) {
- history = input.form.history = [];
- }
- history.push(input.value);
- var vars = {
- tbid: tbid,
- debugcount: debug_count,
- input: input.value
- };
- MochiKit.DOM.showElement(output);
- var d = MochiKit.Async.doSimpleXMLHttpRequest(url, vars);
- d.addCallbacks(function (data) {
- var result = data.responseText;
- output.innerHTML += result;
- input.value = '';
- input.focus();
- }, function (error) {
- showError(error.req.responseText);
- });
- return false;
-}
-
-function showError(msg) {
- var el = $('error-container');
- if (el.innerHTML) {
- el.innerHTML += '<hr noshade>\n' + msg;
- } else {
- el.innerHTML = msg;
- }
- MochiKit.DOM.showElement('error-area');
-}
-
-function clearError() {
- var el = $('error-container');
- el.innerHTML = '';
- MochiKit.DOM.hideElement('error-area');
-}
-
-function expandInput(button) {
- var input = button.form.elements.input;
- stdops = {
- name: 'input',
- style: 'width: 100%',
- autocomplete: 'off'
- };
- if (input.tagName == 'INPUT') {
- var newEl = MochiKit.DOM.TEXTAREA(stdops);
- var text = 'Contract';
- } else {
- stdops['type'] = 'text';
- stdops['onkeypress'] = 'upArrow(this)';
- var newEl = MochiKit.DOM.INPUT(stdops);
- var text = 'Expand';
- }
- newEl.value = input.value;
- newEl.id = input.id;
- MochiKit.DOM.swapDOM(input, newEl);
- newEl.focus();
- button.value = text;
- return false;
-}
-
-function upArrow(input, event) {
- if (window.event) {
- event = window.event;
- }
- if (event.keyCode != 38 && event.keyCode != 40) {
- // not an up- or down-arrow
- return true;
- }
- var dir = event.keyCode == 38 ? 1 : -1;
- var history = input.form.history;
- if (! history) {
- history = input.form.history = [];
- }
- var pos = input.historyPosition || 0;
- if (! pos && dir == -1) {
- return true;
- }
- if (! pos && input.value) {
- history.push(input.value);
- pos = 1;
- }
- pos += dir;
- if (history.length-pos < 0) {
- pos = 1;
- }
- if (history.length-pos > history.length-1) {
- input.value = '';
- return true;
- }
- input.historyPosition = pos;
- var line = history[history.length-pos];
- input.value = line;
-}
-
-function expandLong(anchor) {
- var span = anchor;
- while (span) {
- if (span.style && span.style.display == 'none') {
- break;
- }
- span = span.nextSibling;
- }
- if (! span) {
- return false;
- }
- MochiKit.DOM.showElement(span);
- MochiKit.DOM.hideElement(anchor);
- return false;
-}
diff --git a/lib/paste/evalexception/media/minus.jpg b/lib/paste/evalexception/media/minus.jpg
Binary files differ.
diff --git a/lib/paste/evalexception/media/plus.jpg b/lib/paste/evalexception/media/plus.jpg
Binary files differ.
diff --git a/lib/paste/evalexception/middleware.py b/lib/paste/evalexception/middleware.py
@@ -1,611 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Exception-catching middleware that allows interactive debugging.
-
-This middleware catches all unexpected exceptions. A normal
-traceback, like produced by
-``paste.exceptions.errormiddleware.ErrorMiddleware`` is given, plus
-controls to see local variables and evaluate expressions in a local
-context.
-
-This can only be used in single-process environments, because
-subsequent requests must go back to the same process that the
-exception originally occurred in. Threaded or non-concurrent
-environments both work.
-
-This shouldn't be used in production in any way. That would just be
-silly.
-
-If calling from an XMLHttpRequest call, if the GET variable ``_`` is
-given then it will make the response more compact (and less
-Javascripty), since if you use innerHTML it'll kill your browser. You
-can look for the header X-Debug-URL in your 500 responses if you want
-to see the full debuggable traceback. Also, this URL is printed to
-``wsgi.errors``, so you can open it up in another browser window.
-"""
-import sys
-import os
-import cgi
-import traceback
-from cStringIO import StringIO
-import pprint
-import itertools
-import time
-import re
-from paste.exceptions import errormiddleware, formatter, collector
-from paste import wsgilib
-from paste import urlparser
-from paste import httpexceptions
-from paste import registry
-from paste import request
-from paste import response
-import evalcontext
-
-limit = 200
-
-def html_quote(v):
- """
- Escape HTML characters, plus translate None to ''
- """
- if v is None:
- return ''
- return cgi.escape(str(v), 1)
-
-def preserve_whitespace(v, quote=True):
- """
- Quote a value for HTML, preserving whitespace (translating
- newlines to ``<br>`` and multiple spaces to use `` ``).
-
- If ``quote`` is true, then the value will be HTML quoted first.
- """
- if quote:
- v = html_quote(v)
- v = v.replace('\n', '<br>\n')
- v = re.sub(r'()( +)', _repl_nbsp, v)
- v = re.sub(r'(\n)( +)', _repl_nbsp, v)
- v = re.sub(r'^()( +)', _repl_nbsp, v)
- return '<code>%s</code>' % v
-
-def _repl_nbsp(match):
- if len(match.group(2)) == 1:
- return ' '
- return match.group(1) + ' ' * (len(match.group(2))-1) + ' '
-
-def simplecatcher(application):
- """
- A simple middleware that catches errors and turns them into simple
- tracebacks.
- """
- def simplecatcher_app(environ, start_response):
- try:
- return application(environ, start_response)
- except:
- out = StringIO()
- traceback.print_exc(file=out)
- start_response('500 Server Error',
- [('content-type', 'text/html')],
- sys.exc_info())
- res = out.getvalue()
- return ['<h3>Error</h3><pre>%s</pre>'
- % html_quote(res)]
- return simplecatcher_app
-
-def wsgiapp():
- """
- Turns a function or method into a WSGI application.
- """
- def decorator(func):
- def wsgiapp_wrapper(*args):
- # we get 3 args when this is a method, two when it is
- # a function :(
- if len(args) == 3:
- environ = args[1]
- start_response = args[2]
- args = [args[0]]
- else:
- environ, start_response = args
- args = []
- def application(environ, start_response):
- form = wsgilib.parse_formvars(environ,
- include_get_vars=True)
- headers = response.HeaderDict(
- {'content-type': 'text/html',
- 'status': '200 OK'})
- form['environ'] = environ
- form['headers'] = headers
- res = func(*args, **form.mixed())
- status = headers.pop('status')
- start_response(status, headers.headeritems())
- return [res]
- app = httpexceptions.make_middleware(application)
- app = simplecatcher(app)
- return app(environ, start_response)
- wsgiapp_wrapper.exposed = True
- return wsgiapp_wrapper
- return decorator
-
-def get_debug_info(func):
- """
- A decorator (meant to be used under ``wsgiapp()``) that resolves
- the ``debugcount`` variable to a ``DebugInfo`` object (or gives an
- error if it can't be found).
- """
- def debug_info_replacement(self, **form):
- try:
- if 'debugcount' not in form:
- raise ValueError('You must provide a debugcount parameter')
- debugcount = form.pop('debugcount')
- try:
- debugcount = int(debugcount)
- except ValueError:
- raise ValueError('Bad value for debugcount')
- if debugcount not in self.debug_infos:
- raise ValueError(
- 'Debug %s no longer found (maybe it has expired?)'
- % debugcount)
- debug_info = self.debug_infos[debugcount]
- return func(self, debug_info=debug_info, **form)
- except ValueError, e:
- form['headers']['status'] = '500 Server Error'
- return '<html>There was an error: %s</html>' % html_quote(e)
- return debug_info_replacement
-
-debug_counter = itertools.count(int(time.time()))
-def get_debug_count(environ):
- """
- Return the unique debug count for the current request
- """
- if 'paste.evalexception.debug_count' in environ:
- return environ['paste.evalexception.debug_count']
- else:
- environ['paste.evalexception.debug_count'] = next = debug_counter.next()
- return next
-
-class EvalException(object):
-
- def __init__(self, application, global_conf=None,
- xmlhttp_key=None):
- self.application = application
- self.debug_infos = {}
- if xmlhttp_key is None:
- if global_conf is None:
- xmlhttp_key = '_'
- else:
- xmlhttp_key = global_conf.get('xmlhttp_key', '_')
- self.xmlhttp_key = xmlhttp_key
-
- def __call__(self, environ, start_response):
- assert not environ['wsgi.multiprocess'], (
- "The EvalException middleware is not usable in a "
- "multi-process environment")
- environ['paste.evalexception'] = self
- if environ.get('PATH_INFO', '').startswith('/_debug/'):
- return self.debug(environ, start_response)
- else:
- return self.respond(environ, start_response)
-
- def debug(self, environ, start_response):
- assert request.path_info_pop(environ) == '_debug'
- next_part = request.path_info_pop(environ)
- method = getattr(self, next_part, None)
- if not method:
- exc = httpexceptions.HTTPNotFound(
- '%r not found when parsing %r'
- % (next_part, wsgilib.construct_url(environ)))
- return exc.wsgi_application(environ, start_response)
- if not getattr(method, 'exposed', False):
- exc = httpexceptions.HTTPForbidden(
- '%r not allowed' % next_part)
- return exc.wsgi_application(environ, start_response)
- return method(environ, start_response)
-
- def media(self, environ, start_response):
- """
- Static path where images and other files live
- """
- app = urlparser.StaticURLParser(
- os.path.join(os.path.dirname(__file__), 'media'))
- return app(environ, start_response)
- media.exposed = True
-
- def mochikit(self, environ, start_response):
- """
- Static path where MochiKit lives
- """
- app = urlparser.StaticURLParser(
- os.path.join(os.path.dirname(__file__), 'mochikit'))
- return app(environ, start_response)
- mochikit.exposed = True
-
- def summary(self, environ, start_response):
- """
- Returns a JSON-format summary of all the cached
- exception reports
- """
- start_response('200 OK', [('Content-type', 'text/x-json')])
- data = [];
- items = self.debug_infos.values()
- items.sort(lambda a, b: cmp(a.created, b.created))
- data = [item.json() for item in items]
- return [repr(data)]
- summary.exposed = True
-
- def view(self, environ, start_response):
- """
- View old exception reports
- """
- id = int(request.path_info_pop(environ))
- if id not in self.debug_infos:
- start_response(
- '500 Server Error',
- [('Content-type', 'text/html')])
- return [
- "Traceback by id %s does not exist (maybe "
- "the server has been restarted?)"
- % id]
- debug_info = self.debug_infos[id]
- return debug_info.wsgi_application(environ, start_response)
- view.exposed = True
-
- def make_view_url(self, environ, base_path, count):
- return base_path + '/_debug/view/%s' % count
-
- #@wsgiapp()
- #@get_debug_info
- def show_frame(self, tbid, debug_info, **kw):
- frame = debug_info.frame(int(tbid))
- vars = frame.tb_frame.f_locals
- if vars:
- registry.restorer.restoration_begin(debug_info.counter)
- local_vars = make_table(vars)
- registry.restorer.restoration_end()
- else:
- local_vars = 'No local vars'
- return input_form(tbid, debug_info) + local_vars
-
- show_frame = wsgiapp()(get_debug_info(show_frame))
-
- #@wsgiapp()
- #@get_debug_info
- def exec_input(self, tbid, debug_info, input, **kw):
- if not input.strip():
- return ''
- input = input.rstrip() + '\n'
- frame = debug_info.frame(int(tbid))
- vars = frame.tb_frame.f_locals
- glob_vars = frame.tb_frame.f_globals
- context = evalcontext.EvalContext(vars, glob_vars)
- registry.restorer.restoration_begin(debug_info.counter)
- output = context.exec_expr(input)
- registry.restorer.restoration_end()
- input_html = formatter.str2html(input)
- return ('<code style="color: #060">>>></code> '
- '<code>%s</code><br>\n%s'
- % (preserve_whitespace(input_html, quote=False),
- preserve_whitespace(output)))
-
- exec_input = wsgiapp()(get_debug_info(exec_input))
-
- def respond(self, environ, start_response):
- if environ.get('paste.throw_errors'):
- return self.application(environ, start_response)
- base_path = request.construct_url(environ, with_path_info=False,
- with_query_string=False)
- environ['paste.throw_errors'] = True
- started = []
- def detect_start_response(status, headers, exc_info=None):
- try:
- return start_response(status, headers, exc_info)
- except:
- raise
- else:
- started.append(True)
- try:
- __traceback_supplement__ = errormiddleware.Supplement, self, environ
- app_iter = self.application(environ, detect_start_response)
- try:
- return_iter = list(app_iter)
- return return_iter
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- except:
- exc_info = sys.exc_info()
- for expected in environ.get('paste.expected_exceptions', []):
- if isinstance(exc_info[1], expected):
- raise
-
- # Tell the Registry to save its StackedObjectProxies current state
- # for later restoration
- registry.restorer.save_registry_state(environ)
-
- count = get_debug_count(environ)
- view_uri = self.make_view_url(environ, base_path, count)
- if not started:
- headers = [('content-type', 'text/html')]
- headers.append(('X-Debug-URL', view_uri))
- start_response('500 Internal Server Error',
- headers,
- exc_info)
- environ['wsgi.errors'].write('Debug at: %s\n' % view_uri)
-
- exc_data = collector.collect_exception(*exc_info)
- debug_info = DebugInfo(count, exc_info, exc_data, base_path,
- environ, view_uri)
- assert count not in self.debug_infos
- self.debug_infos[count] = debug_info
-
- if self.xmlhttp_key:
- get_vars = wsgilib.parse_querystring(environ)
- if dict(get_vars).get(self.xmlhttp_key):
- exc_data = collector.collect_exception(*exc_info)
- html = formatter.format_html(
- exc_data, include_hidden_frames=False,
- include_reusable=False, show_extra_data=False)
- return [html]
-
- # @@: it would be nice to deal with bad content types here
- return debug_info.content()
-
- def exception_handler(self, exc_info, environ):
- simple_html_error = False
- if self.xmlhttp_key:
- get_vars = wsgilib.parse_querystring(environ)
- if dict(get_vars).get(self.xmlhttp_key):
- simple_html_error = True
- return errormiddleware.handle_exception(
- exc_info, environ['wsgi.errors'],
- html=True,
- debug_mode=True,
- simple_html_error=simple_html_error)
-
-class DebugInfo(object):
-
- def __init__(self, counter, exc_info, exc_data, base_path,
- environ, view_uri):
- self.counter = counter
- self.exc_data = exc_data
- self.base_path = base_path
- self.environ = environ
- self.view_uri = view_uri
- self.created = time.time()
- self.exc_type, self.exc_value, self.tb = exc_info
- __exception_formatter__ = 1
- self.frames = []
- n = 0
- tb = self.tb
- while tb is not None and (limit is None or n < limit):
- if tb.tb_frame.f_locals.get('__exception_formatter__'):
- # Stop recursion. @@: should make a fake ExceptionFrame
- break
- self.frames.append(tb)
- tb = tb.tb_next
- n += 1
-
- def json(self):
- """Return the JSON-able representation of this object"""
- return {
- 'uri': self.view_uri,
- 'created': time.strftime('%c', time.gmtime(self.created)),
- 'created_timestamp': self.created,
- 'exception_type': str(self.exc_type),
- 'exception': str(self.exc_value),
- }
-
- def frame(self, tbid):
- for frame in self.frames:
- if id(frame) == tbid:
- return frame
- else:
- raise ValueError, (
- "No frame by id %s found from %r" % (tbid, self.frames))
-
- def wsgi_application(self, environ, start_response):
- start_response('200 OK', [('content-type', 'text/html')])
- return self.content()
-
- def content(self):
- html = format_eval_html(self.exc_data, self.base_path, self.counter)
- head_html = (formatter.error_css + formatter.hide_display_js)
- head_html += self.eval_javascript()
- repost_button = make_repost_button(self.environ)
- page = error_template % {
- 'repost_button': repost_button or '',
- 'head_html': head_html,
- 'body': html}
- return [page]
-
- def eval_javascript(self):
- base_path = self.base_path + '/_debug'
- return (
- '<script type="text/javascript" src="%s/mochikit/MochiKit.js">'
- '</script>\n'
- '<script type="text/javascript" src="%s/media/debug.js">'
- '</script>\n'
- '<script type="text/javascript">\n'
- 'debug_base = %r;\n'
- 'debug_count = %r;\n'
- '</script>\n'
- % (base_path, base_path, base_path, self.counter))
-
-class EvalHTMLFormatter(formatter.HTMLFormatter):
-
- def __init__(self, base_path, counter, **kw):
- super(EvalHTMLFormatter, self).__init__(**kw)
- self.base_path = base_path
- self.counter = counter
-
- def format_source_line(self, filename, frame):
- line = formatter.HTMLFormatter.format_source_line(
- self, filename, frame)
- return (line +
- ' <a href="#" class="switch_source" '
- 'tbid="%s" onClick="return showFrame(this)"> '
- '<img src="%s/_debug/media/plus.jpg" border=0 width=9 '
- 'height=9> </a>'
- % (frame.tbid, self.base_path))
-
-def make_table(items):
- if isinstance(items, dict):
- items = items.items()
- items.sort()
- rows = []
- i = 0
- for name, value in items:
- i += 1
- out = StringIO()
- try:
- pprint.pprint(value, out)
- except Exception, e:
- print >> out, 'Error: %s' % e
- value = html_quote(out.getvalue())
- if len(value) > 100:
- # @@: This can actually break the HTML :(
- # should I truncate before quoting?
- orig_value = value
- value = value[:100]
- value += '<a class="switch_source" style="background-color: #999" href="#" onclick="return expandLong(this)">...</a>'
- value += '<span style="display: none">%s</span>' % orig_value[100:]
- value = formatter.make_wrappable(value)
- if i % 2:
- attr = ' class="even"'
- else:
- attr = ' class="odd"'
- rows.append('<tr%s style="vertical-align: top;"><td>'
- '<b>%s</b></td><td style="overflow: auto">%s<td></tr>'
- % (attr, html_quote(name),
- preserve_whitespace(value, quote=False)))
- return '<table>%s</table>' % (
- '\n'.join(rows))
-
-def format_eval_html(exc_data, base_path, counter):
- short_formatter = EvalHTMLFormatter(
- base_path=base_path,
- counter=counter,
- include_reusable=False)
- short_er = short_formatter.format_collected_data(exc_data)
- long_formatter = EvalHTMLFormatter(
- base_path=base_path,
- counter=counter,
- show_hidden_frames=True,
- show_extra_data=False,
- include_reusable=False)
- long_er = long_formatter.format_collected_data(exc_data)
- text_er = formatter.format_text(exc_data, show_hidden_frames=True)
- if short_formatter.filter_frames(exc_data.frames) != \
- long_formatter.filter_frames(exc_data.frames):
- # Only display the full traceback when it differs from the
- # short version
- full_traceback_html = """
- <br>
- <script type="text/javascript">
- show_button('full_traceback', 'full traceback')
- </script>
- <div id="full_traceback" class="hidden-data">
- %s
- </div>
- """ % long_er
- else:
- full_traceback_html = ''
-
- return """
- %s
- %s
- <br>
- <script type="text/javascript">
- show_button('text_version', 'text version')
- </script>
- <div id="text_version" class="hidden-data">
- <textarea style="width: 100%%" rows=10 cols=60>%s</textarea>
- </div>
- """ % (short_er, full_traceback_html, cgi.escape(text_er))
-
-def make_repost_button(environ):
- url = request.construct_url(environ)
- if environ['REQUEST_METHOD'] == 'GET':
- return ('<button onclick="window.location.href=%r">'
- 'Re-GET Page</button><br>' % url)
- else:
- # @@: I'd like to reconstruct this, but I can't because
- # the POST body is probably lost at this point, and
- # I can't get it back :(
- return None
- # @@: Use or lose the following code block
- """
- fields = []
- for name, value in wsgilib.parse_formvars(
- environ, include_get_vars=False).items():
- if hasattr(value, 'filename'):
- # @@: Arg, we'll just submit the body, and leave out
- # the filename :(
- value = value.value
- fields.append(
- '<input type="hidden" name="%s" value="%s">'
- % (html_quote(name), html_quote(value)))
- return '''
-<form action="%s" method="POST">
-%s
-<input type="submit" value="Re-POST Page">
-</form>''' % (url, '\n'.join(fields))
-"""
-
-
-def input_form(tbid, debug_info):
- return '''
-<form action="#" method="POST"
- onsubmit="return submitInput($(\'submit_%(tbid)s\'), %(tbid)s)">
-<div id="exec-output-%(tbid)s" style="width: 95%%;
- padding: 5px; margin: 5px; border: 2px solid #000;
- display: none"></div>
-<input type="text" name="input" id="debug_input_%(tbid)s"
- style="width: 100%%"
- autocomplete="off" onkeypress="upArrow(this, event)"><br>
-<input type="submit" value="Execute" name="submitbutton"
- onclick="return submitInput(this, %(tbid)s)"
- id="submit_%(tbid)s"
- input-from="debug_input_%(tbid)s"
- output-to="exec-output-%(tbid)s">
-<input type="submit" value="Expand"
- onclick="return expandInput(this)">
-</form>
- ''' % {'tbid': tbid}
-
-error_template = '''
-<html>
-<head>
- <title>Server Error</title>
- %(head_html)s
-</head>
-<body>
-
-<div id="error-area" style="display: none; background-color: #600; color: #fff; border: 2px solid black">
-<div id="error-container"></div>
-<button onclick="return clearError()">clear this</button>
-</div>
-
-%(repost_button)s
-
-%(body)s
-
-</body>
-</html>
-'''
-
-def make_eval_exception(app, global_conf, xmlhttp_key=None):
- """
- Wraps the application in an interactive debugger.
-
- This debugger is a major security hole, and should only be
- used during development.
-
- xmlhttp_key is a string that, if present in QUERY_STRING,
- indicates that the request is an XMLHttp request, and the
- Javascript/interactive debugger should not be returned. (If you
- try to put the debugger somewhere with innerHTML, you will often
- crash the browser)
- """
- if xmlhttp_key is None:
- xmlhttp_key = global_conf.get('xmlhttp_key', '_')
- return EvalException(app, xmlhttp_key=xmlhttp_key)
-
diff --git a/lib/paste/evalexception/mochikit/MochiKit.js b/lib/paste/evalexception/mochikit/MochiKit.js
@@ -1,4802 +0,0 @@
-/***
-
- MochiKit.MochiKit 1.3.1 : PACKED VERSION
-
- THIS FILE IS AUTOMATICALLY GENERATED. If creating patches, please
- diff against the source tree, not this file.
-
- See <http://mochikit.com/> for documentation, downloads, license, etc.
-
- (c) 2005 Bob Ippolito. All rights Reserved.
-
-***/
-
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.Base");
-}
-if(typeof (MochiKit)=="undefined"){
-MochiKit={};
-}
-if(typeof (MochiKit.Base)=="undefined"){
-MochiKit.Base={};
-}
-MochiKit.Base.VERSION="1.3.1";
-MochiKit.Base.NAME="MochiKit.Base";
-MochiKit.Base.update=function(_1,_2){
-if(_1===null){
-_1={};
-}
-for(var i=1;i<arguments.length;i++){
-var o=arguments[i];
-if(typeof (o)!="undefined"&&o!==null){
-for(var k in o){
-_1[k]=o[k];
-}
-}
-}
-return _1;
-};
-MochiKit.Base.update(MochiKit.Base,{__repr__:function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-},toString:function(){
-return this.__repr__();
-},counter:function(n){
-if(arguments.length===0){
-n=1;
-}
-return function(){
-return n++;
-};
-},clone:function(_7){
-var me=arguments.callee;
-if(arguments.length==1){
-me.prototype=_7;
-return new me();
-}
-},flattenArguments:function(_9){
-var res=[];
-var m=MochiKit.Base;
-var _12=m.extend(null,arguments);
-while(_12.length){
-var o=_12.shift();
-if(o&&typeof (o)=="object"&&typeof (o.length)=="number"){
-for(var i=o.length-1;i>=0;i--){
-_12.unshift(o[i]);
-}
-}else{
-res.push(o);
-}
-}
-return res;
-},extend:function(_13,obj,_15){
-if(!_15){
-_15=0;
-}
-if(obj){
-var l=obj.length;
-if(typeof (l)!="number"){
-if(typeof (MochiKit.Iter)!="undefined"){
-obj=MochiKit.Iter.list(obj);
-l=obj.length;
-}else{
-throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
-}
-}
-if(!_13){
-_13=[];
-}
-for(var i=_15;i<l;i++){
-_13.push(obj[i]);
-}
-}
-return _13;
-},updatetree:function(_17,obj){
-if(_17===null){
-_17={};
-}
-for(var i=1;i<arguments.length;i++){
-var o=arguments[i];
-if(typeof (o)!="undefined"&&o!==null){
-for(var k in o){
-var v=o[k];
-if(typeof (_17[k])=="object"&&typeof (v)=="object"){
-arguments.callee(_17[k],v);
-}else{
-_17[k]=v;
-}
-}
-}
-}
-return _17;
-},setdefault:function(_19,obj){
-if(_19===null){
-_19={};
-}
-for(var i=1;i<arguments.length;i++){
-var o=arguments[i];
-for(var k in o){
-if(!(k in _19)){
-_19[k]=o[k];
-}
-}
-}
-return _19;
-},keys:function(obj){
-var _20=[];
-for(var _21 in obj){
-_20.push(_21);
-}
-return _20;
-},items:function(obj){
-var _22=[];
-var e;
-for(var _24 in obj){
-var v;
-try{
-v=obj[_24];
-}
-catch(e){
-continue;
-}
-_22.push([_24,v]);
-}
-return _22;
-},_newNamedError:function(_25,_26,_27){
-_27.prototype=new MochiKit.Base.NamedError(_25.NAME+"."+_26);
-_25[_26]=_27;
-},operator:{truth:function(a){
-return !!a;
-},lognot:function(a){
-return !a;
-},identity:function(a){
-return a;
-},not:function(a){
-return ~a;
-},neg:function(a){
-return -a;
-},add:function(a,b){
-return a+b;
-},sub:function(a,b){
-return a-b;
-},div:function(a,b){
-return a/b;
-},mod:function(a,b){
-return a%b;
-},mul:function(a,b){
-return a*b;
-},and:function(a,b){
-return a&b;
-},or:function(a,b){
-return a|b;
-},xor:function(a,b){
-return a^b;
-},lshift:function(a,b){
-return a<<b;
-},rshift:function(a,b){
-return a>>b;
-},zrshift:function(a,b){
-return a>>>b;
-},eq:function(a,b){
-return a==b;
-},ne:function(a,b){
-return a!=b;
-},gt:function(a,b){
-return a>b;
-},ge:function(a,b){
-return a>=b;
-},lt:function(a,b){
-return a<b;
-},le:function(a,b){
-return a<=b;
-},ceq:function(a,b){
-return MochiKit.Base.compare(a,b)===0;
-},cne:function(a,b){
-return MochiKit.Base.compare(a,b)!==0;
-},cgt:function(a,b){
-return MochiKit.Base.compare(a,b)==1;
-},cge:function(a,b){
-return MochiKit.Base.compare(a,b)!=-1;
-},clt:function(a,b){
-return MochiKit.Base.compare(a,b)==-1;
-},cle:function(a,b){
-return MochiKit.Base.compare(a,b)!=1;
-},logand:function(a,b){
-return a&&b;
-},logor:function(a,b){
-return a||b;
-},contains:function(a,b){
-return b in a;
-}},forwardCall:function(_30){
-return function(){
-return this[_30].apply(this,arguments);
-};
-},itemgetter:function(_31){
-return function(arg){
-return arg[_31];
-};
-},typeMatcher:function(){
-var _33={};
-for(var i=0;i<arguments.length;i++){
-var typ=arguments[i];
-_33[typ]=typ;
-}
-return function(){
-for(var i=0;i<arguments.length;i++){
-if(!(typeof (arguments[i]) in _33)){
-return false;
-}
-}
-return true;
-};
-},isNull:function(){
-for(var i=0;i<arguments.length;i++){
-if(arguments[i]!==null){
-return false;
-}
-}
-return true;
-},isUndefinedOrNull:function(){
-for(var i=0;i<arguments.length;i++){
-var o=arguments[i];
-if(!(typeof (o)=="undefined"||o===null)){
-return false;
-}
-}
-return true;
-},isEmpty:function(obj){
-return !MochiKit.Base.isNotEmpty.apply(this,arguments);
-},isNotEmpty:function(obj){
-for(var i=0;i<arguments.length;i++){
-var o=arguments[i];
-if(!(o&&o.length)){
-return false;
-}
-}
-return true;
-},isArrayLike:function(){
-for(var i=0;i<arguments.length;i++){
-var o=arguments[i];
-var typ=typeof (o);
-if((typ!="object"&&!(typ=="function"&&typeof (o.item)=="function"))||o===null||typeof (o.length)!="number"){
-return false;
-}
-}
-return true;
-},isDateLike:function(){
-for(var i=0;i<arguments.length;i++){
-var o=arguments[i];
-if(typeof (o)!="object"||o===null||typeof (o.getTime)!="function"){
-return false;
-}
-}
-return true;
-},xmap:function(fn){
-if(fn===null){
-return MochiKit.Base.extend(null,arguments,1);
-}
-var _36=[];
-for(var i=1;i<arguments.length;i++){
-_36.push(fn(arguments[i]));
-}
-return _36;
-},map:function(fn,lst){
-var m=MochiKit.Base;
-var itr=MochiKit.Iter;
-var _39=m.isArrayLike;
-if(arguments.length<=2){
-if(!_39(lst)){
-if(itr){
-lst=itr.list(lst);
-if(fn===null){
-return lst;
-}
-}else{
-throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
-}
-}
-if(fn===null){
-return m.extend(null,lst);
-}
-var _40=[];
-for(var i=0;i<lst.length;i++){
-_40.push(fn(lst[i]));
-}
-return _40;
-}else{
-if(fn===null){
-fn=Array;
-}
-var _41=null;
-for(i=1;i<arguments.length;i++){
-if(!_39(arguments[i])){
-if(itr){
-return itr.list(itr.imap.apply(null,arguments));
-}else{
-throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
-}
-}
-var l=arguments[i].length;
-if(_41===null||_41>l){
-_41=l;
-}
-}
-_40=[];
-for(i=0;i<_41;i++){
-var _42=[];
-for(var j=1;j<arguments.length;j++){
-_42.push(arguments[j][i]);
-}
-_40.push(fn.apply(this,_42));
-}
-return _40;
-}
-},xfilter:function(fn){
-var _44=[];
-if(fn===null){
-fn=MochiKit.Base.operator.truth;
-}
-for(var i=1;i<arguments.length;i++){
-var o=arguments[i];
-if(fn(o)){
-_44.push(o);
-}
-}
-return _44;
-},filter:function(fn,lst,_45){
-var _46=[];
-var m=MochiKit.Base;
-if(!m.isArrayLike(lst)){
-if(MochiKit.Iter){
-lst=MochiKit.Iter.list(lst);
-}else{
-throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
-}
-}
-if(fn===null){
-fn=m.operator.truth;
-}
-if(typeof (Array.prototype.filter)=="function"){
-return Array.prototype.filter.call(lst,fn,_45);
-}else{
-if(typeof (_45)=="undefined"||_45===null){
-for(var i=0;i<lst.length;i++){
-var o=lst[i];
-if(fn(o)){
-_46.push(o);
-}
-}
-}else{
-for(i=0;i<lst.length;i++){
-o=lst[i];
-if(fn.call(_45,o)){
-_46.push(o);
-}
-}
-}
-}
-return _46;
-},_wrapDumbFunction:function(_47){
-return function(){
-switch(arguments.length){
-case 0:
-return _47();
-case 1:
-return _47(arguments[0]);
-case 2:
-return _47(arguments[0],arguments[1]);
-case 3:
-return _47(arguments[0],arguments[1],arguments[2]);
-}
-var _48=[];
-for(var i=0;i<arguments.length;i++){
-_48.push("arguments["+i+"]");
-}
-return eval("(func("+_48.join(",")+"))");
-};
-},method:function(_49,_50){
-var m=MochiKit.Base;
-return m.bind.apply(this,m.extend([_50,_49],arguments,2));
-},bind:function(_51,_52){
-if(typeof (_51)=="string"){
-_51=_52[_51];
-}
-var _53=_51.im_func;
-var _54=_51.im_preargs;
-var _55=_51.im_self;
-var m=MochiKit.Base;
-if(typeof (_51)=="function"&&typeof (_51.apply)=="undefined"){
-_51=m._wrapDumbFunction(_51);
-}
-if(typeof (_53)!="function"){
-_53=_51;
-}
-if(typeof (_52)!="undefined"){
-_55=_52;
-}
-if(typeof (_54)=="undefined"){
-_54=[];
-}else{
-_54=_54.slice();
-}
-m.extend(_54,arguments,2);
-var _56=function(){
-var _57=arguments;
-var me=arguments.callee;
-if(me.im_preargs.length>0){
-_57=m.concat(me.im_preargs,_57);
-}
-var _52=me.im_self;
-if(!_52){
-_52=this;
-}
-return me.im_func.apply(_52,_57);
-};
-_56.im_self=_55;
-_56.im_func=_53;
-_56.im_preargs=_54;
-return _56;
-},bindMethods:function(_58){
-var _59=MochiKit.Base.bind;
-for(var k in _58){
-var _60=_58[k];
-if(typeof (_60)=="function"){
-_58[k]=_59(_60,_58);
-}
-}
-},registerComparator:function(_61,_62,_63,_64){
-MochiKit.Base.comparatorRegistry.register(_61,_62,_63,_64);
-},_primitives:{"boolean":true,"string":true,"number":true},compare:function(a,b){
-if(a==b){
-return 0;
-}
-var _65=(typeof (a)=="undefined"||a===null);
-var _66=(typeof (b)=="undefined"||b===null);
-if(_65&&_66){
-return 0;
-}else{
-if(_65){
-return -1;
-}else{
-if(_66){
-return 1;
-}
-}
-}
-var m=MochiKit.Base;
-var _67=m._primitives;
-if(!(typeof (a) in _67&&typeof (b) in _67)){
-try{
-return m.comparatorRegistry.match(a,b);
-}
-catch(e){
-if(e!=m.NotFound){
-throw e;
-}
-}
-}
-if(a<b){
-return -1;
-}else{
-if(a>b){
-return 1;
-}
-}
-var _68=m.repr;
-throw new TypeError(_68(a)+" and "+_68(b)+" can not be compared");
-},compareDateLike:function(a,b){
-return MochiKit.Base.compare(a.getTime(),b.getTime());
-},compareArrayLike:function(a,b){
-var _69=MochiKit.Base.compare;
-var _70=a.length;
-var _71=0;
-if(_70>b.length){
-_71=1;
-_70=b.length;
-}else{
-if(_70<b.length){
-_71=-1;
-}
-}
-for(var i=0;i<_70;i++){
-var cmp=_69(a[i],b[i]);
-if(cmp){
-return cmp;
-}
-}
-return _71;
-},registerRepr:function(_73,_74,_75,_76){
-MochiKit.Base.reprRegistry.register(_73,_74,_75,_76);
-},repr:function(o){
-if(typeof (o)=="undefined"){
-return "undefined";
-}else{
-if(o===null){
-return "null";
-}
-}
-try{
-if(typeof (o.__repr__)=="function"){
-return o.__repr__();
-}else{
-if(typeof (o.repr)=="function"&&o.repr!=arguments.callee){
-return o.repr();
-}
-}
-return MochiKit.Base.reprRegistry.match(o);
-}
-catch(e){
-if(typeof (o.NAME)=="string"&&(o.toString==Function.prototype.toString||o.toString==Object.prototype.toString)){
-return o.NAME;
-}
-}
-try{
-var _77=(o+"");
-}
-catch(e){
-return "["+typeof (o)+"]";
-}
-if(typeof (o)=="function"){
-o=_77.replace(/^\s+/,"");
-var idx=o.indexOf("{");
-if(idx!=-1){
-o=o.substr(0,idx)+"{...}";
-}
-}
-return _77;
-},reprArrayLike:function(o){
-var m=MochiKit.Base;
-return "["+m.map(m.repr,o).join(", ")+"]";
-},reprString:function(o){
-return ("\""+o.replace(/(["\\])/g,"\\$1")+"\"").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r");
-},reprNumber:function(o){
-return o+"";
-},registerJSON:function(_79,_80,_81,_82){
-MochiKit.Base.jsonRegistry.register(_79,_80,_81,_82);
-},evalJSON:function(){
-return eval("("+arguments[0]+")");
-},serializeJSON:function(o){
-var _83=typeof (o);
-if(_83=="undefined"){
-return "undefined";
-}else{
-if(_83=="number"||_83=="boolean"){
-return o+"";
-}else{
-if(o===null){
-return "null";
-}
-}
-}
-var m=MochiKit.Base;
-var _84=m.reprString;
-if(_83=="string"){
-return _84(o);
-}
-var me=arguments.callee;
-var _85;
-if(typeof (o.__json__)=="function"){
-_85=o.__json__();
-if(o!==_85){
-return me(_85);
-}
-}
-if(typeof (o.json)=="function"){
-_85=o.json();
-if(o!==_85){
-return me(_85);
-}
-}
-if(_83!="function"&&typeof (o.length)=="number"){
-var res=[];
-for(var i=0;i<o.length;i++){
-var val=me(o[i]);
-if(typeof (val)!="string"){
-val="undefined";
-}
-res.push(val);
-}
-return "["+res.join(", ")+"]";
-}
-try{
-_85=m.jsonRegistry.match(o);
-return me(_85);
-}
-catch(e){
-if(e!=m.NotFound){
-throw e;
-}
-}
-if(_83=="function"){
-return null;
-}
-res=[];
-for(var k in o){
-var _87;
-if(typeof (k)=="number"){
-_87="\""+k+"\"";
-}else{
-if(typeof (k)=="string"){
-_87=_84(k);
-}else{
-continue;
-}
-}
-val=me(o[k]);
-if(typeof (val)!="string"){
-continue;
-}
-res.push(_87+":"+val);
-}
-return "{"+res.join(", ")+"}";
-},objEqual:function(a,b){
-return (MochiKit.Base.compare(a,b)===0);
-},arrayEqual:function(_88,arr){
-if(_88.length!=arr.length){
-return false;
-}
-return (MochiKit.Base.compare(_88,arr)===0);
-},concat:function(){
-var _90=[];
-var _91=MochiKit.Base.extend;
-for(var i=0;i<arguments.length;i++){
-_91(_90,arguments[i]);
-}
-return _90;
-},keyComparator:function(key){
-var m=MochiKit.Base;
-var _93=m.compare;
-if(arguments.length==1){
-return function(a,b){
-return _93(a[key],b[key]);
-};
-}
-var _94=m.extend(null,arguments);
-return function(a,b){
-var _95=0;
-for(var i=0;(_95===0)&&(i<_94.length);i++){
-var key=_94[i];
-_95=_93(a[key],b[key]);
-}
-return _95;
-};
-},reverseKeyComparator:function(key){
-var _96=MochiKit.Base.keyComparator.apply(this,arguments);
-return function(a,b){
-return _96(b,a);
-};
-},partial:function(_97){
-var m=MochiKit.Base;
-return m.bind.apply(this,m.extend([_97,undefined],arguments,1));
-},listMinMax:function(_98,lst){
-if(lst.length===0){
-return null;
-}
-var cur=lst[0];
-var _100=MochiKit.Base.compare;
-for(var i=1;i<lst.length;i++){
-var o=lst[i];
-if(_100(o,cur)==_98){
-cur=o;
-}
-}
-return cur;
-},objMax:function(){
-return MochiKit.Base.listMinMax(1,arguments);
-},objMin:function(){
-return MochiKit.Base.listMinMax(-1,arguments);
-},findIdentical:function(lst,_101,_102,end){
-if(typeof (end)=="undefined"||end===null){
-end=lst.length;
-}
-for(var i=(_102||0);i<end;i++){
-if(lst[i]===_101){
-return i;
-}
-}
-return -1;
-},findValue:function(lst,_104,_105,end){
-if(typeof (end)=="undefined"||end===null){
-end=lst.length;
-}
-var cmp=MochiKit.Base.compare;
-for(var i=(_105||0);i<end;i++){
-if(cmp(lst[i],_104)===0){
-return i;
-}
-}
-return -1;
-},nodeWalk:function(node,_107){
-var _108=[node];
-var _109=MochiKit.Base.extend;
-while(_108.length){
-var res=_107(_108.shift());
-if(res){
-_109(_108,res);
-}
-}
-},nameFunctions:function(_110){
-var base=_110.NAME;
-if(typeof (base)=="undefined"){
-base="";
-}else{
-base=base+".";
-}
-for(var name in _110){
-var o=_110[name];
-if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){
-try{
-o.NAME=base+name;
-}
-catch(e){
-}
-}
-}
-},queryString:function(_113,_114){
-if(typeof (MochiKit.DOM)!="undefined"&&arguments.length==1&&(typeof (_113)=="string"||(typeof (_113.nodeType)!="undefined"&&_113.nodeType>0))){
-var kv=MochiKit.DOM.formContents(_113);
-_113=kv[0];
-_114=kv[1];
-}else{
-if(arguments.length==1){
-var o=_113;
-_113=[];
-_114=[];
-for(var k in o){
-var v=o[k];
-if(typeof (v)!="function"){
-_113.push(k);
-_114.push(v);
-}
-}
-}
-}
-var rval=[];
-var len=Math.min(_113.length,_114.length);
-var _118=MochiKit.Base.urlEncode;
-for(var i=0;i<len;i++){
-v=_114[i];
-if(typeof (v)!="undefined"&&v!==null){
-rval.push(_118(_113[i])+"="+_118(v));
-}
-}
-return rval.join("&");
-},parseQueryString:function(_119,_120){
-var _121=_119.replace(/\+/g,"%20").split("&");
-var o={};
-var _122;
-if(typeof (decodeURIComponent)!="undefined"){
-_122=decodeURIComponent;
-}else{
-_122=unescape;
-}
-if(_120){
-for(var i=0;i<_121.length;i++){
-var pair=_121[i].split("=");
-var name=_122(pair[0]);
-var arr=o[name];
-if(!(arr instanceof Array)){
-arr=[];
-o[name]=arr;
-}
-arr.push(_122(pair[1]));
-}
-}else{
-for(i=0;i<_121.length;i++){
-pair=_121[i].split("=");
-o[_122(pair[0])]=_122(pair[1]);
-}
-}
-return o;
-}});
-MochiKit.Base.AdapterRegistry=function(){
-this.pairs=[];
-};
-MochiKit.Base.AdapterRegistry.prototype={register:function(name,_124,wrap,_126){
-if(_126){
-this.pairs.unshift([name,_124,wrap]);
-}else{
-this.pairs.push([name,_124,wrap]);
-}
-},match:function(){
-for(var i=0;i<this.pairs.length;i++){
-var pair=this.pairs[i];
-if(pair[1].apply(this,arguments)){
-return pair[2].apply(this,arguments);
-}
-}
-throw MochiKit.Base.NotFound;
-},unregister:function(name){
-for(var i=0;i<this.pairs.length;i++){
-var pair=this.pairs[i];
-if(pair[0]==name){
-this.pairs.splice(i,1);
-return true;
-}
-}
-return false;
-}};
-MochiKit.Base.EXPORT=["counter","clone","extend","update","updatetree","setdefault","keys","items","NamedError","operator","forwardCall","itemgetter","typeMatcher","isCallable","isUndefined","isUndefinedOrNull","isNull","isEmpty","isNotEmpty","isArrayLike","isDateLike","xmap","map","xfilter","filter","bind","bindMethods","NotFound","AdapterRegistry","registerComparator","compare","registerRepr","repr","objEqual","arrayEqual","concat","keyComparator","reverseKeyComparator","partial","merge","listMinMax","listMax","listMin","objMax","objMin","nodeWalk","zip","urlEncode","queryString","serializeJSON","registerJSON","evalJSON","parseQueryString","findValue","findIdentical","flattenArguments","method"];
-MochiKit.Base.EXPORT_OK=["nameFunctions","comparatorRegistry","reprRegistry","jsonRegistry","compareDateLike","compareArrayLike","reprArrayLike","reprString","reprNumber"];
-MochiKit.Base._exportSymbols=function(_127,_128){
-if(typeof (MochiKit.__export__)=="undefined"){
-MochiKit.__export__=(MochiKit.__compat__||(typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined"));
-}
-if(!MochiKit.__export__){
-return;
-}
-var all=_128.EXPORT_TAGS[":all"];
-for(var i=0;i<all.length;i++){
-_127[all[i]]=_128[all[i]];
-}
-};
-MochiKit.Base.__new__=function(){
-var m=this;
-m.forward=m.forwardCall;
-m.find=m.findValue;
-if(typeof (encodeURIComponent)!="undefined"){
-m.urlEncode=function(_130){
-return encodeURIComponent(_130).replace(/\'/g,"%27");
-};
-}else{
-m.urlEncode=function(_131){
-return escape(_131).replace(/\+/g,"%2B").replace(/\"/g,"%22").rval.replace(/\'/g,"%27");
-};
-}
-m.NamedError=function(name){
-this.message=name;
-this.name=name;
-};
-m.NamedError.prototype=new Error();
-m.update(m.NamedError.prototype,{repr:function(){
-if(this.message&&this.message!=this.name){
-return this.name+"("+m.repr(this.message)+")";
-}else{
-return this.name+"()";
-}
-},toString:m.forwardCall("repr")});
-m.NotFound=new m.NamedError("MochiKit.Base.NotFound");
-m.listMax=m.partial(m.listMinMax,1);
-m.listMin=m.partial(m.listMinMax,-1);
-m.isCallable=m.typeMatcher("function");
-m.isUndefined=m.typeMatcher("undefined");
-m.merge=m.partial(m.update,null);
-m.zip=m.partial(m.map,null);
-m.comparatorRegistry=new m.AdapterRegistry();
-m.registerComparator("dateLike",m.isDateLike,m.compareDateLike);
-m.registerComparator("arrayLike",m.isArrayLike,m.compareArrayLike);
-m.reprRegistry=new m.AdapterRegistry();
-m.registerRepr("arrayLike",m.isArrayLike,m.reprArrayLike);
-m.registerRepr("string",m.typeMatcher("string"),m.reprString);
-m.registerRepr("numbers",m.typeMatcher("number","boolean"),m.reprNumber);
-m.jsonRegistry=new m.AdapterRegistry();
-var all=m.concat(m.EXPORT,m.EXPORT_OK);
-m.EXPORT_TAGS={":common":m.concat(m.EXPORT_OK),":all":all};
-m.nameFunctions(this);
-};
-MochiKit.Base.__new__();
-if(!MochiKit.__compat__){
-compare=MochiKit.Base.compare;
-}
-MochiKit.Base._exportSymbols(this,MochiKit.Base);
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.Iter");
-dojo.require("MochiKit.Base");
-}
-if(typeof (JSAN)!="undefined"){
-JSAN.use("MochiKit.Base",[]);
-}
-try{
-if(typeof (MochiKit.Base)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "MochiKit.Iter depends on MochiKit.Base!";
-}
-if(typeof (MochiKit.Iter)=="undefined"){
-MochiKit.Iter={};
-}
-MochiKit.Iter.NAME="MochiKit.Iter";
-MochiKit.Iter.VERSION="1.3.1";
-MochiKit.Base.update(MochiKit.Iter,{__repr__:function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-},toString:function(){
-return this.__repr__();
-},registerIteratorFactory:function(name,_132,_133,_134){
-MochiKit.Iter.iteratorRegistry.register(name,_132,_133,_134);
-},iter:function(_135,_136){
-var self=MochiKit.Iter;
-if(arguments.length==2){
-return self.takewhile(function(a){
-return a!=_136;
-},_135);
-}
-if(typeof (_135.next)=="function"){
-return _135;
-}else{
-if(typeof (_135.iter)=="function"){
-return _135.iter();
-}
-}
-try{
-return self.iteratorRegistry.match(_135);
-}
-catch(e){
-var m=MochiKit.Base;
-if(e==m.NotFound){
-e=new TypeError(typeof (_135)+": "+m.repr(_135)+" is not iterable");
-}
-throw e;
-}
-},count:function(n){
-if(!n){
-n=0;
-}
-var m=MochiKit.Base;
-return {repr:function(){
-return "count("+n+")";
-},toString:m.forwardCall("repr"),next:m.counter(n)};
-},cycle:function(p){
-var self=MochiKit.Iter;
-var m=MochiKit.Base;
-var lst=[];
-var _139=self.iter(p);
-return {repr:function(){
-return "cycle(...)";
-},toString:m.forwardCall("repr"),next:function(){
-try{
-var rval=_139.next();
-lst.push(rval);
-return rval;
-}
-catch(e){
-if(e!=self.StopIteration){
-throw e;
-}
-if(lst.length===0){
-this.next=function(){
-throw self.StopIteration;
-};
-}else{
-var i=-1;
-this.next=function(){
-i=(i+1)%lst.length;
-return lst[i];
-};
-}
-return this.next();
-}
-}};
-},repeat:function(elem,n){
-var m=MochiKit.Base;
-if(typeof (n)=="undefined"){
-return {repr:function(){
-return "repeat("+m.repr(elem)+")";
-},toString:m.forwardCall("repr"),next:function(){
-return elem;
-}};
-}
-return {repr:function(){
-return "repeat("+m.repr(elem)+", "+n+")";
-},toString:m.forwardCall("repr"),next:function(){
-if(n<=0){
-throw MochiKit.Iter.StopIteration;
-}
-n-=1;
-return elem;
-}};
-},next:function(_141){
-return _141.next();
-},izip:function(p,q){
-var m=MochiKit.Base;
-var next=MochiKit.Iter.next;
-var _144=m.map(iter,arguments);
-return {repr:function(){
-return "izip(...)";
-},toString:m.forwardCall("repr"),next:function(){
-return m.map(next,_144);
-}};
-},ifilter:function(pred,seq){
-var m=MochiKit.Base;
-seq=MochiKit.Iter.iter(seq);
-if(pred===null){
-pred=m.operator.truth;
-}
-return {repr:function(){
-return "ifilter(...)";
-},toString:m.forwardCall("repr"),next:function(){
-while(true){
-var rval=seq.next();
-if(pred(rval)){
-return rval;
-}
-}
-return undefined;
-}};
-},ifilterfalse:function(pred,seq){
-var m=MochiKit.Base;
-seq=MochiKit.Iter.iter(seq);
-if(pred===null){
-pred=m.operator.truth;
-}
-return {repr:function(){
-return "ifilterfalse(...)";
-},toString:m.forwardCall("repr"),next:function(){
-while(true){
-var rval=seq.next();
-if(!pred(rval)){
-return rval;
-}
-}
-return undefined;
-}};
-},islice:function(seq){
-var self=MochiKit.Iter;
-var m=MochiKit.Base;
-seq=self.iter(seq);
-var _147=0;
-var stop=0;
-var step=1;
-var i=-1;
-if(arguments.length==2){
-stop=arguments[1];
-}else{
-if(arguments.length==3){
-_147=arguments[1];
-stop=arguments[2];
-}else{
-_147=arguments[1];
-stop=arguments[2];
-step=arguments[3];
-}
-}
-return {repr:function(){
-return "islice("+["...",_147,stop,step].join(", ")+")";
-},toString:m.forwardCall("repr"),next:function(){
-var rval;
-while(i<_147){
-rval=seq.next();
-i++;
-}
-if(_147>=stop){
-throw self.StopIteration;
-}
-_147+=step;
-return rval;
-}};
-},imap:function(fun,p,q){
-var m=MochiKit.Base;
-var self=MochiKit.Iter;
-var _151=m.map(self.iter,m.extend(null,arguments,1));
-var map=m.map;
-var next=self.next;
-return {repr:function(){
-return "imap(...)";
-},toString:m.forwardCall("repr"),next:function(){
-return fun.apply(this,map(next,_151));
-}};
-},applymap:function(fun,seq,self){
-seq=MochiKit.Iter.iter(seq);
-var m=MochiKit.Base;
-return {repr:function(){
-return "applymap(...)";
-},toString:m.forwardCall("repr"),next:function(){
-return fun.apply(self,seq.next());
-}};
-},chain:function(p,q){
-var self=MochiKit.Iter;
-var m=MochiKit.Base;
-if(arguments.length==1){
-return self.iter(arguments[0]);
-}
-var _153=m.map(self.iter,arguments);
-return {repr:function(){
-return "chain(...)";
-},toString:m.forwardCall("repr"),next:function(){
-while(_153.length>1){
-try{
-return _153[0].next();
-}
-catch(e){
-if(e!=self.StopIteration){
-throw e;
-}
-_153.shift();
-}
-}
-if(_153.length==1){
-var arg=_153.shift();
-this.next=m.bind("next",arg);
-return this.next();
-}
-throw self.StopIteration;
-}};
-},takewhile:function(pred,seq){
-var self=MochiKit.Iter;
-seq=self.iter(seq);
-return {repr:function(){
-return "takewhile(...)";
-},toString:MochiKit.Base.forwardCall("repr"),next:function(){
-var rval=seq.next();
-if(!pred(rval)){
-this.next=function(){
-throw self.StopIteration;
-};
-this.next();
-}
-return rval;
-}};
-},dropwhile:function(pred,seq){
-seq=MochiKit.Iter.iter(seq);
-var m=MochiKit.Base;
-var bind=m.bind;
-return {"repr":function(){
-return "dropwhile(...)";
-},"toString":m.forwardCall("repr"),"next":function(){
-while(true){
-var rval=seq.next();
-if(!pred(rval)){
-break;
-}
-}
-this.next=bind("next",seq);
-return rval;
-}};
-},_tee:function(_155,sync,_157){
-sync.pos[_155]=-1;
-var m=MochiKit.Base;
-var _158=m.listMin;
-return {repr:function(){
-return "tee("+_155+", ...)";
-},toString:m.forwardCall("repr"),next:function(){
-var rval;
-var i=sync.pos[_155];
-if(i==sync.max){
-rval=_157.next();
-sync.deque.push(rval);
-sync.max+=1;
-sync.pos[_155]+=1;
-}else{
-rval=sync.deque[i-sync.min];
-sync.pos[_155]+=1;
-if(i==sync.min&&_158(sync.pos)!=sync.min){
-sync.min+=1;
-sync.deque.shift();
-}
-}
-return rval;
-}};
-},tee:function(_159,n){
-var rval=[];
-var sync={"pos":[],"deque":[],"max":-1,"min":-1};
-if(arguments.length==1){
-n=2;
-}
-var self=MochiKit.Iter;
-_159=self.iter(_159);
-var _tee=self._tee;
-for(var i=0;i<n;i++){
-rval.push(_tee(i,sync,_159));
-}
-return rval;
-},list:function(_161){
-var m=MochiKit.Base;
-if(typeof (_161.slice)=="function"){
-return _161.slice();
-}else{
-if(m.isArrayLike(_161)){
-return m.concat(_161);
-}
-}
-var self=MochiKit.Iter;
-_161=self.iter(_161);
-var rval=[];
-try{
-while(true){
-rval.push(_161.next());
-}
-}
-catch(e){
-if(e!=self.StopIteration){
-throw e;
-}
-return rval;
-}
-return undefined;
-},reduce:function(fn,_162,_163){
-var i=0;
-var x=_163;
-var self=MochiKit.Iter;
-_162=self.iter(_162);
-if(arguments.length<3){
-try{
-x=_162.next();
-}
-catch(e){
-if(e==self.StopIteration){
-e=new TypeError("reduce() of empty sequence with no initial value");
-}
-throw e;
-}
-i++;
-}
-try{
-while(true){
-x=fn(x,_162.next());
-}
-}
-catch(e){
-if(e!=self.StopIteration){
-throw e;
-}
-}
-return x;
-},range:function(){
-var _165=0;
-var stop=0;
-var step=1;
-if(arguments.length==1){
-stop=arguments[0];
-}else{
-if(arguments.length==2){
-_165=arguments[0];
-stop=arguments[1];
-}else{
-if(arguments.length==3){
-_165=arguments[0];
-stop=arguments[1];
-step=arguments[2];
-}else{
-throw new TypeError("range() takes 1, 2, or 3 arguments!");
-}
-}
-}
-if(step===0){
-throw new TypeError("range() step must not be 0");
-}
-return {next:function(){
-if((step>0&&_165>=stop)||(step<0&&_165<=stop)){
-throw MochiKit.Iter.StopIteration;
-}
-var rval=_165;
-_165+=step;
-return rval;
-},repr:function(){
-return "range("+[_165,stop,step].join(", ")+")";
-},toString:MochiKit.Base.forwardCall("repr")};
-},sum:function(_166,_167){
-var x=_167||0;
-var self=MochiKit.Iter;
-_166=self.iter(_166);
-try{
-while(true){
-x+=_166.next();
-}
-}
-catch(e){
-if(e!=self.StopIteration){
-throw e;
-}
-}
-return x;
-},exhaust:function(_168){
-var self=MochiKit.Iter;
-_168=self.iter(_168);
-try{
-while(true){
-_168.next();
-}
-}
-catch(e){
-if(e!=self.StopIteration){
-throw e;
-}
-}
-},forEach:function(_169,func,self){
-var m=MochiKit.Base;
-if(arguments.length>2){
-func=m.bind(func,self);
-}
-if(m.isArrayLike(_169)){
-try{
-for(var i=0;i<_169.length;i++){
-func(_169[i]);
-}
-}
-catch(e){
-if(e!=MochiKit.Iter.StopIteration){
-throw e;
-}
-}
-}else{
-self=MochiKit.Iter;
-self.exhaust(self.imap(func,_169));
-}
-},every:function(_171,func){
-var self=MochiKit.Iter;
-try{
-self.ifilterfalse(func,_171).next();
-return false;
-}
-catch(e){
-if(e!=self.StopIteration){
-throw e;
-}
-return true;
-}
-},sorted:function(_172,cmp){
-var rval=MochiKit.Iter.list(_172);
-if(arguments.length==1){
-cmp=MochiKit.Base.compare;
-}
-rval.sort(cmp);
-return rval;
-},reversed:function(_173){
-var rval=MochiKit.Iter.list(_173);
-rval.reverse();
-return rval;
-},some:function(_174,func){
-var self=MochiKit.Iter;
-try{
-self.ifilter(func,_174).next();
-return true;
-}
-catch(e){
-if(e!=self.StopIteration){
-throw e;
-}
-return false;
-}
-},iextend:function(lst,_175){
-if(MochiKit.Base.isArrayLike(_175)){
-for(var i=0;i<_175.length;i++){
-lst.push(_175[i]);
-}
-}else{
-var self=MochiKit.Iter;
-_175=self.iter(_175);
-try{
-while(true){
-lst.push(_175.next());
-}
-}
-catch(e){
-if(e!=self.StopIteration){
-throw e;
-}
-}
-}
-return lst;
-},groupby:function(_176,_177){
-var m=MochiKit.Base;
-var self=MochiKit.Iter;
-if(arguments.length<2){
-_177=m.operator.identity;
-}
-_176=self.iter(_176);
-var pk=undefined;
-var k=undefined;
-var v;
-function fetch(){
-v=_176.next();
-k=_177(v);
-}
-function eat(){
-var ret=v;
-v=undefined;
-return ret;
-}
-var _180=true;
-return {repr:function(){
-return "groupby(...)";
-},next:function(){
-while(k==pk){
-fetch();
-if(_180){
-_180=false;
-break;
-}
-}
-pk=k;
-return [k,{next:function(){
-if(v==undefined){
-fetch();
-}
-if(k!=pk){
-throw self.StopIteration;
-}
-return eat();
-}}];
-}};
-},groupby_as_array:function(_181,_182){
-var m=MochiKit.Base;
-var self=MochiKit.Iter;
-if(arguments.length<2){
-_182=m.operator.identity;
-}
-_181=self.iter(_181);
-var _183=[];
-var _184=true;
-var _185;
-while(true){
-try{
-var _186=_181.next();
-var key=_182(_186);
-}
-catch(e){
-if(e==self.StopIteration){
-break;
-}
-throw e;
-}
-if(_184||key!=_185){
-var _187=[];
-_183.push([key,_187]);
-}
-_187.push(_186);
-_184=false;
-_185=key;
-}
-return _183;
-},arrayLikeIter:function(_188){
-var i=0;
-return {repr:function(){
-return "arrayLikeIter(...)";
-},toString:MochiKit.Base.forwardCall("repr"),next:function(){
-if(i>=_188.length){
-throw MochiKit.Iter.StopIteration;
-}
-return _188[i++];
-}};
-},hasIterateNext:function(_189){
-return (_189&&typeof (_189.iterateNext)=="function");
-},iterateNextIter:function(_190){
-return {repr:function(){
-return "iterateNextIter(...)";
-},toString:MochiKit.Base.forwardCall("repr"),next:function(){
-var rval=_190.iterateNext();
-if(rval===null||rval===undefined){
-throw MochiKit.Iter.StopIteration;
-}
-return rval;
-}};
-}});
-MochiKit.Iter.EXPORT_OK=["iteratorRegistry","arrayLikeIter","hasIterateNext","iterateNextIter",];
-MochiKit.Iter.EXPORT=["StopIteration","registerIteratorFactory","iter","count","cycle","repeat","next","izip","ifilter","ifilterfalse","islice","imap","applymap","chain","takewhile","dropwhile","tee","list","reduce","range","sum","exhaust","forEach","every","sorted","reversed","some","iextend","groupby","groupby_as_array"];
-MochiKit.Iter.__new__=function(){
-var m=MochiKit.Base;
-this.StopIteration=new m.NamedError("StopIteration");
-this.iteratorRegistry=new m.AdapterRegistry();
-this.registerIteratorFactory("arrayLike",m.isArrayLike,this.arrayLikeIter);
-this.registerIteratorFactory("iterateNext",this.hasIterateNext,this.iterateNextIter);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-m.nameFunctions(this);
-};
-MochiKit.Iter.__new__();
-if(!MochiKit.__compat__){
-reduce=MochiKit.Iter.reduce;
-}
-MochiKit.Base._exportSymbols(this,MochiKit.Iter);
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.Logging");
-dojo.require("MochiKit.Base");
-}
-if(typeof (JSAN)!="undefined"){
-JSAN.use("MochiKit.Base",[]);
-}
-try{
-if(typeof (MochiKit.Base)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "MochiKit.Logging depends on MochiKit.Base!";
-}
-if(typeof (MochiKit.Logging)=="undefined"){
-MochiKit.Logging={};
-}
-MochiKit.Logging.NAME="MochiKit.Logging";
-MochiKit.Logging.VERSION="1.3.1";
-MochiKit.Logging.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-MochiKit.Logging.toString=function(){
-return this.__repr__();
-};
-MochiKit.Logging.EXPORT=["LogLevel","LogMessage","Logger","alertListener","logger","log","logError","logDebug","logFatal","logWarning"];
-MochiKit.Logging.EXPORT_OK=["logLevelAtLeast","isLogMessage","compareLogMessage"];
-MochiKit.Logging.LogMessage=function(num,_192,info){
-this.num=num;
-this.level=_192;
-this.info=info;
-this.timestamp=new Date();
-};
-MochiKit.Logging.LogMessage.prototype={repr:function(){
-var m=MochiKit.Base;
-return "LogMessage("+m.map(m.repr,[this.num,this.level,this.info]).join(", ")+")";
-},toString:MochiKit.Base.forwardCall("repr")};
-MochiKit.Base.update(MochiKit.Logging,{logLevelAtLeast:function(_194){
-var self=MochiKit.Logging;
-if(typeof (_194)=="string"){
-_194=self.LogLevel[_194];
-}
-return function(msg){
-var _196=msg.level;
-if(typeof (_196)=="string"){
-_196=self.LogLevel[_196];
-}
-return _196>=_194;
-};
-},isLogMessage:function(){
-var _197=MochiKit.Logging.LogMessage;
-for(var i=0;i<arguments.length;i++){
-if(!(arguments[i] instanceof _197)){
-return false;
-}
-}
-return true;
-},compareLogMessage:function(a,b){
-return MochiKit.Base.compare([a.level,a.info],[b.level,b.info]);
-},alertListener:function(msg){
-alert("num: "+msg.num+"\nlevel: "+msg.level+"\ninfo: "+msg.info.join(" "));
-}});
-MochiKit.Logging.Logger=function(_198){
-this.counter=0;
-if(typeof (_198)=="undefined"||_198===null){
-_198=-1;
-}
-this.maxSize=_198;
-this._messages=[];
-this.listeners={};
-this.useNativeConsole=false;
-};
-MochiKit.Logging.Logger.prototype={clear:function(){
-this._messages.splice(0,this._messages.length);
-},logToConsole:function(msg){
-if(typeof (window)!="undefined"&&window.console&&window.console.log){
-window.console.log(msg);
-}else{
-if(typeof (opera)!="undefined"&&opera.postError){
-opera.postError(msg);
-}else{
-if(typeof (printfire)=="function"){
-printfire(msg);
-}
-}
-}
-},dispatchListeners:function(msg){
-for(var k in this.listeners){
-var pair=this.listeners[k];
-if(pair.ident!=k||(pair[0]&&!pair[0](msg))){
-continue;
-}
-pair[1](msg);
-}
-},addListener:function(_199,_200,_201){
-if(typeof (_200)=="string"){
-_200=MochiKit.Logging.logLevelAtLeast(_200);
-}
-var _202=[_200,_201];
-_202.ident=_199;
-this.listeners[_199]=_202;
-},removeListener:function(_203){
-delete this.listeners[_203];
-},baseLog:function(_204,_205){
-var msg=new MochiKit.Logging.LogMessage(this.counter,_204,MochiKit.Base.extend(null,arguments,1));
-this._messages.push(msg);
-this.dispatchListeners(msg);
-if(this.useNativeConsole){
-this.logToConsole(msg.level+": "+msg.info.join(" "));
-}
-this.counter+=1;
-while(this.maxSize>=0&&this._messages.length>this.maxSize){
-this._messages.shift();
-}
-},getMessages:function(_206){
-var _207=0;
-if(!(typeof (_206)=="undefined"||_206===null)){
-_207=Math.max(0,this._messages.length-_206);
-}
-return this._messages.slice(_207);
-},getMessageText:function(_208){
-if(typeof (_208)=="undefined"||_208===null){
-_208=30;
-}
-var _209=this.getMessages(_208);
-if(_209.length){
-var lst=map(function(m){
-return "\n ["+m.num+"] "+m.level+": "+m.info.join(" ");
-},_209);
-lst.unshift("LAST "+_209.length+" MESSAGES:");
-return lst.join("");
-}
-return "";
-},debuggingBookmarklet:function(_210){
-if(typeof (MochiKit.LoggingPane)=="undefined"){
-alert(this.getMessageText());
-}else{
-MochiKit.LoggingPane.createLoggingPane(_210||false);
-}
-}};
-MochiKit.Logging.__new__=function(){
-this.LogLevel={ERROR:40,FATAL:50,WARNING:30,INFO:20,DEBUG:10};
-var m=MochiKit.Base;
-m.registerComparator("LogMessage",this.isLogMessage,this.compareLogMessage);
-var _211=m.partial;
-var _212=this.Logger;
-var _213=_212.prototype.baseLog;
-m.update(this.Logger.prototype,{debug:_211(_213,"DEBUG"),log:_211(_213,"INFO"),error:_211(_213,"ERROR"),fatal:_211(_213,"FATAL"),warning:_211(_213,"WARNING")});
-var self=this;
-var _214=function(name){
-return function(){
-self.logger[name].apply(self.logger,arguments);
-};
-};
-this.log=_214("log");
-this.logError=_214("error");
-this.logDebug=_214("debug");
-this.logFatal=_214("fatal");
-this.logWarning=_214("warning");
-this.logger=new _212();
-this.logger.useNativeConsole=true;
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-m.nameFunctions(this);
-};
-if(typeof (printfire)=="undefined"&&typeof (document)!="undefined"&&document.createEvent&&typeof (dispatchEvent)!="undefined"){
-printfire=function(){
-printfire.args=arguments;
-var ev=document.createEvent("Events");
-ev.initEvent("printfire",false,true);
-dispatchEvent(ev);
-};
-}
-MochiKit.Logging.__new__();
-MochiKit.Base._exportSymbols(this,MochiKit.Logging);
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.DateTime");
-}
-if(typeof (MochiKit)=="undefined"){
-MochiKit={};
-}
-if(typeof (MochiKit.DateTime)=="undefined"){
-MochiKit.DateTime={};
-}
-MochiKit.DateTime.NAME="MochiKit.DateTime";
-MochiKit.DateTime.VERSION="1.3.1";
-MochiKit.DateTime.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-MochiKit.DateTime.toString=function(){
-return this.__repr__();
-};
-MochiKit.DateTime.isoDate=function(str){
-str=str+"";
-if(typeof (str)!="string"||str.length===0){
-return null;
-}
-var iso=str.split("-");
-if(iso.length===0){
-return null;
-}
-return new Date(iso[0],iso[1]-1,iso[2]);
-};
-MochiKit.DateTime._isoRegexp=/(\d{4,})(?:-(\d{1,2})(?:-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d+))?)?(?:(Z)|([+-])(\d{1,2})(?::(\d{1,2}))?)?)?)?)?/;
-MochiKit.DateTime.isoTimestamp=function(str){
-str=str+"";
-if(typeof (str)!="string"||str.length===0){
-return null;
-}
-var res=str.match(MochiKit.DateTime._isoRegexp);
-if(typeof (res)=="undefined"||res===null){
-return null;
-}
-var year,month,day,hour,min,sec,msec;
-year=parseInt(res[1],10);
-if(typeof (res[2])=="undefined"||res[2]===""){
-return new Date(year);
-}
-month=parseInt(res[2],10)-1;
-day=parseInt(res[3],10);
-if(typeof (res[4])=="undefined"||res[4]===""){
-return new Date(year,month,day);
-}
-hour=parseInt(res[4],10);
-min=parseInt(res[5],10);
-sec=(typeof (res[6])!="undefined"&&res[6]!=="")?parseInt(res[6],10):0;
-if(typeof (res[7])!="undefined"&&res[7]!==""){
-msec=Math.round(1000*parseFloat("0."+res[7]));
-}else{
-msec=0;
-}
-if((typeof (res[8])=="undefined"||res[8]==="")&&(typeof (res[9])=="undefined"||res[9]==="")){
-return new Date(year,month,day,hour,min,sec,msec);
-}
-var ofs;
-if(typeof (res[9])!="undefined"&&res[9]!==""){
-ofs=parseInt(res[10],10)*3600000;
-if(typeof (res[11])!="undefined"&&res[11]!==""){
-ofs+=parseInt(res[11],10)*60000;
-}
-if(res[9]=="-"){
-ofs=-ofs;
-}
-}else{
-ofs=0;
-}
-return new Date(Date.UTC(year,month,day,hour,min,sec,msec)-ofs);
-};
-MochiKit.DateTime.toISOTime=function(date,_221){
-if(typeof (date)=="undefined"||date===null){
-return null;
-}
-var hh=date.getHours();
-var mm=date.getMinutes();
-var ss=date.getSeconds();
-var lst=[((_221&&(hh<10))?"0"+hh:hh),((mm<10)?"0"+mm:mm),((ss<10)?"0"+ss:ss)];
-return lst.join(":");
-};
-MochiKit.DateTime.toISOTimestamp=function(date,_225){
-if(typeof (date)=="undefined"||date===null){
-return null;
-}
-var sep=_225?"T":" ";
-var foot=_225?"Z":"";
-if(_225){
-date=new Date(date.getTime()+(date.getTimezoneOffset()*60000));
-}
-return MochiKit.DateTime.toISODate(date)+sep+MochiKit.DateTime.toISOTime(date,_225)+foot;
-};
-MochiKit.DateTime.toISODate=function(date){
-if(typeof (date)=="undefined"||date===null){
-return null;
-}
-var _228=MochiKit.DateTime._padTwo;
-return [date.getFullYear(),_228(date.getMonth()+1),_228(date.getDate())].join("-");
-};
-MochiKit.DateTime.americanDate=function(d){
-d=d+"";
-if(typeof (d)!="string"||d.length===0){
-return null;
-}
-var a=d.split("/");
-return new Date(a[2],a[0]-1,a[1]);
-};
-MochiKit.DateTime._padTwo=function(n){
-return (n>9)?n:"0"+n;
-};
-MochiKit.DateTime.toPaddedAmericanDate=function(d){
-if(typeof (d)=="undefined"||d===null){
-return null;
-}
-var _230=MochiKit.DateTime._padTwo;
-return [_230(d.getMonth()+1),_230(d.getDate()),d.getFullYear()].join("/");
-};
-MochiKit.DateTime.toAmericanDate=function(d){
-if(typeof (d)=="undefined"||d===null){
-return null;
-}
-return [d.getMonth()+1,d.getDate(),d.getFullYear()].join("/");
-};
-MochiKit.DateTime.EXPORT=["isoDate","isoTimestamp","toISOTime","toISOTimestamp","toISODate","americanDate","toPaddedAmericanDate","toAmericanDate"];
-MochiKit.DateTime.EXPORT_OK=[];
-MochiKit.DateTime.EXPORT_TAGS={":common":MochiKit.DateTime.EXPORT,":all":MochiKit.DateTime.EXPORT};
-MochiKit.DateTime.__new__=function(){
-var base=this.NAME+".";
-for(var k in this){
-var o=this[k];
-if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){
-try{
-o.NAME=base+k;
-}
-catch(e){
-}
-}
-}
-};
-MochiKit.DateTime.__new__();
-if(typeof (MochiKit.Base)!="undefined"){
-MochiKit.Base._exportSymbols(this,MochiKit.DateTime);
-}else{
-(function(_231,_232){
-if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){
-var all=_232.EXPORT_TAGS[":all"];
-for(var i=0;i<all.length;i++){
-_231[all[i]]=_232[all[i]];
-}
-}
-})(this,MochiKit.DateTime);
-}
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.Format");
-}
-if(typeof (MochiKit)=="undefined"){
-MochiKit={};
-}
-if(typeof (MochiKit.Format)=="undefined"){
-MochiKit.Format={};
-}
-MochiKit.Format.NAME="MochiKit.Format";
-MochiKit.Format.VERSION="1.3.1";
-MochiKit.Format.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-MochiKit.Format.toString=function(){
-return this.__repr__();
-};
-MochiKit.Format._numberFormatter=function(_233,_234,_235,_236,_237,_238,_239,_240,_241){
-return function(num){
-num=parseFloat(num);
-if(typeof (num)=="undefined"||num===null||isNaN(num)){
-return _233;
-}
-var _242=_234;
-var _243=_235;
-if(num<0){
-num=-num;
-}else{
-_242=_242.replace(/-/,"");
-}
-var me=arguments.callee;
-var fmt=MochiKit.Format.formatLocale(_236);
-if(_237){
-num=num*100;
-_243=fmt.percent+_243;
-}
-num=MochiKit.Format.roundToFixed(num,_238);
-var _245=num.split(/\./);
-var _246=_245[0];
-var frac=(_245.length==1)?"":_245[1];
-var res="";
-while(_246.length<_239){
-_246="0"+_246;
-}
-if(_240){
-while(_246.length>_240){
-var i=_246.length-_240;
-res=fmt.separator+_246.substring(i,_246.length)+res;
-_246=_246.substring(0,i);
-}
-}
-res=_246+res;
-if(_238>0){
-while(frac.length<_241){
-frac=frac+"0";
-}
-res=res+fmt.decimal+frac;
-}
-return _242+res+_243;
-};
-};
-MochiKit.Format.numberFormatter=function(_248,_249,_250){
-if(typeof (_249)=="undefined"){
-_249="";
-}
-var _251=_248.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/);
-if(!_251){
-throw TypeError("Invalid pattern");
-}
-var _252=_248.substr(0,_251.index);
-var _253=_248.substr(_251.index+_251[0].length);
-if(_252.search(/-/)==-1){
-_252=_252+"-";
-}
-var _254=_251[1];
-var frac=(typeof (_251[2])=="string"&&_251[2]!="")?_251[2]:"";
-var _255=(typeof (_251[3])=="string"&&_251[3]!="");
-var tmp=_254.split(/,/);
-var _257;
-if(typeof (_250)=="undefined"){
-_250="default";
-}
-if(tmp.length==1){
-_257=null;
-}else{
-_257=tmp[1].length;
-}
-var _258=_254.length-_254.replace(/0/g,"").length;
-var _259=frac.length-frac.replace(/0/g,"").length;
-var _260=frac.length;
-var rval=MochiKit.Format._numberFormatter(_249,_252,_253,_250,_255,_260,_258,_257,_259);
-var m=MochiKit.Base;
-if(m){
-var fn=arguments.callee;
-var args=m.concat(arguments);
-rval.repr=function(){
-return [self.NAME,"(",map(m.repr,args).join(", "),")"].join("");
-};
-}
-return rval;
-};
-MochiKit.Format.formatLocale=function(_262){
-if(typeof (_262)=="undefined"||_262===null){
-_262="default";
-}
-if(typeof (_262)=="string"){
-var rval=MochiKit.Format.LOCALE[_262];
-if(typeof (rval)=="string"){
-rval=arguments.callee(rval);
-MochiKit.Format.LOCALE[_262]=rval;
-}
-return rval;
-}else{
-return _262;
-}
-};
-MochiKit.Format.twoDigitAverage=function(_263,_264){
-if(_264){
-var res=_263/_264;
-if(!isNaN(res)){
-return MochiKit.Format.twoDigitFloat(_263/_264);
-}
-}
-return "0";
-};
-MochiKit.Format.twoDigitFloat=function(_265){
-var sign=(_265<0?"-":"");
-var s=Math.floor(Math.abs(_265)*100).toString();
-if(s=="0"){
-return s;
-}
-if(s.length<3){
-while(s.charAt(s.length-1)=="0"){
-s=s.substring(0,s.length-1);
-}
-return sign+"0."+s;
-}
-var head=sign+s.substring(0,s.length-2);
-var tail=s.substring(s.length-2,s.length);
-if(tail=="00"){
-return head;
-}else{
-if(tail.charAt(1)=="0"){
-return head+"."+tail.charAt(0);
-}else{
-return head+"."+tail;
-}
-}
-};
-MochiKit.Format.lstrip=function(str,_270){
-str=str+"";
-if(typeof (str)!="string"){
-return null;
-}
-if(!_270){
-return str.replace(/^\s+/,"");
-}else{
-return str.replace(new RegExp("^["+_270+"]+"),"");
-}
-};
-MochiKit.Format.rstrip=function(str,_271){
-str=str+"";
-if(typeof (str)!="string"){
-return null;
-}
-if(!_271){
-return str.replace(/\s+$/,"");
-}else{
-return str.replace(new RegExp("["+_271+"]+$"),"");
-}
-};
-MochiKit.Format.strip=function(str,_272){
-var self=MochiKit.Format;
-return self.rstrip(self.lstrip(str,_272),_272);
-};
-MochiKit.Format.truncToFixed=function(_273,_274){
-_273=Math.floor(_273*Math.pow(10,_274));
-var res=(_273*Math.pow(10,-_274)).toFixed(_274);
-if(res.charAt(0)=="."){
-res="0"+res;
-}
-return res;
-};
-MochiKit.Format.roundToFixed=function(_275,_276){
-return MochiKit.Format.truncToFixed(_275+0.5*Math.pow(10,-_276),_276);
-};
-MochiKit.Format.percentFormat=function(_277){
-return MochiKit.Format.twoDigitFloat(100*_277)+"%";
-};
-MochiKit.Format.EXPORT=["truncToFixed","roundToFixed","numberFormatter","formatLocale","twoDigitAverage","twoDigitFloat","percentFormat","lstrip","rstrip","strip"];
-MochiKit.Format.LOCALE={en_US:{separator:",",decimal:".",percent:"%"},de_DE:{separator:".",decimal:",",percent:"%"},fr_FR:{separator:" ",decimal:",",percent:"%"},"default":"en_US"};
-MochiKit.Format.EXPORT_OK=[];
-MochiKit.Format.EXPORT_TAGS={":all":MochiKit.Format.EXPORT,":common":MochiKit.Format.EXPORT};
-MochiKit.Format.__new__=function(){
-var base=this.NAME+".";
-var k,v,o;
-for(k in this.LOCALE){
-o=this.LOCALE[k];
-if(typeof (o)=="object"){
-o.repr=function(){
-return this.NAME;
-};
-o.NAME=base+"LOCALE."+k;
-}
-}
-for(k in this){
-o=this[k];
-if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){
-try{
-o.NAME=base+k;
-}
-catch(e){
-}
-}
-}
-};
-MochiKit.Format.__new__();
-if(typeof (MochiKit.Base)!="undefined"){
-MochiKit.Base._exportSymbols(this,MochiKit.Format);
-}else{
-(function(_278,_279){
-if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){
-var all=_279.EXPORT_TAGS[":all"];
-for(var i=0;i<all.length;i++){
-_278[all[i]]=_279[all[i]];
-}
-}
-})(this,MochiKit.Format);
-}
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.Async");
-dojo.require("MochiKit.Base");
-}
-if(typeof (JSAN)!="undefined"){
-JSAN.use("MochiKit.Base",[]);
-}
-try{
-if(typeof (MochiKit.Base)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "MochiKit.Async depends on MochiKit.Base!";
-}
-if(typeof (MochiKit.Async)=="undefined"){
-MochiKit.Async={};
-}
-MochiKit.Async.NAME="MochiKit.Async";
-MochiKit.Async.VERSION="1.3.1";
-MochiKit.Async.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-MochiKit.Async.toString=function(){
-return this.__repr__();
-};
-MochiKit.Async.Deferred=function(_280){
-this.chain=[];
-this.id=this._nextId();
-this.fired=-1;
-this.paused=0;
-this.results=[null,null];
-this.canceller=_280;
-this.silentlyCancelled=false;
-this.chained=false;
-};
-MochiKit.Async.Deferred.prototype={repr:function(){
-var _281;
-if(this.fired==-1){
-_281="unfired";
-}else{
-if(this.fired===0){
-_281="success";
-}else{
-_281="error";
-}
-}
-return "Deferred("+this.id+", "+_281+")";
-},toString:MochiKit.Base.forwardCall("repr"),_nextId:MochiKit.Base.counter(),cancel:function(){
-var self=MochiKit.Async;
-if(this.fired==-1){
-if(this.canceller){
-this.canceller(this);
-}else{
-this.silentlyCancelled=true;
-}
-if(this.fired==-1){
-this.errback(new self.CancelledError(this));
-}
-}else{
-if((this.fired===0)&&(this.results[0] instanceof self.Deferred)){
-this.results[0].cancel();
-}
-}
-},_pause:function(){
-this.paused++;
-},_unpause:function(){
-this.paused--;
-if((this.paused===0)&&(this.fired>=0)){
-this._fire();
-}
-},_continue:function(res){
-this._resback(res);
-this._unpause();
-},_resback:function(res){
-this.fired=((res instanceof Error)?1:0);
-this.results[this.fired]=res;
-this._fire();
-},_check:function(){
-if(this.fired!=-1){
-if(!this.silentlyCancelled){
-throw new MochiKit.Async.AlreadyCalledError(this);
-}
-this.silentlyCancelled=false;
-return;
-}
-},callback:function(res){
-this._check();
-if(res instanceof MochiKit.Async.Deferred){
-throw new Error("Deferred instances can only be chained if they are the result of a callback");
-}
-this._resback(res);
-},errback:function(res){
-this._check();
-var self=MochiKit.Async;
-if(res instanceof self.Deferred){
-throw new Error("Deferred instances can only be chained if they are the result of a callback");
-}
-if(!(res instanceof Error)){
-res=new self.GenericError(res);
-}
-this._resback(res);
-},addBoth:function(fn){
-if(arguments.length>1){
-fn=MochiKit.Base.partial.apply(null,arguments);
-}
-return this.addCallbacks(fn,fn);
-},addCallback:function(fn){
-if(arguments.length>1){
-fn=MochiKit.Base.partial.apply(null,arguments);
-}
-return this.addCallbacks(fn,null);
-},addErrback:function(fn){
-if(arguments.length>1){
-fn=MochiKit.Base.partial.apply(null,arguments);
-}
-return this.addCallbacks(null,fn);
-},addCallbacks:function(cb,eb){
-if(this.chained){
-throw new Error("Chained Deferreds can not be re-used");
-}
-this.chain.push([cb,eb]);
-if(this.fired>=0){
-this._fire();
-}
-return this;
-},_fire:function(){
-var _284=this.chain;
-var _285=this.fired;
-var res=this.results[_285];
-var self=this;
-var cb=null;
-while(_284.length>0&&this.paused===0){
-var pair=_284.shift();
-var f=pair[_285];
-if(f===null){
-continue;
-}
-try{
-res=f(res);
-_285=((res instanceof Error)?1:0);
-if(res instanceof MochiKit.Async.Deferred){
-cb=function(res){
-self._continue(res);
-};
-this._pause();
-}
-}
-catch(err){
-_285=1;
-if(!(err instanceof Error)){
-err=new MochiKit.Async.GenericError(err);
-}
-res=err;
-}
-}
-this.fired=_285;
-this.results[_285]=res;
-if(cb&&this.paused){
-res.addBoth(cb);
-res.chained=true;
-}
-}};
-MochiKit.Base.update(MochiKit.Async,{evalJSONRequest:function(){
-return eval("("+arguments[0].responseText+")");
-},succeed:function(_287){
-var d=new MochiKit.Async.Deferred();
-d.callback.apply(d,arguments);
-return d;
-},fail:function(_288){
-var d=new MochiKit.Async.Deferred();
-d.errback.apply(d,arguments);
-return d;
-},getXMLHttpRequest:function(){
-var self=arguments.callee;
-if(!self.XMLHttpRequest){
-var _289=[function(){
-return new XMLHttpRequest();
-},function(){
-return new ActiveXObject("Msxml2.XMLHTTP");
-},function(){
-return new ActiveXObject("Microsoft.XMLHTTP");
-},function(){
-return new ActiveXObject("Msxml2.XMLHTTP.4.0");
-},function(){
-throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
-}];
-for(var i=0;i<_289.length;i++){
-var func=_289[i];
-try{
-self.XMLHttpRequest=func;
-return func();
-}
-catch(e){
-}
-}
-}
-return self.XMLHttpRequest();
-},_nothing:function(){
-},_xhr_onreadystatechange:function(d){
-if(this.readyState==4){
-try{
-this.onreadystatechange=null;
-}
-catch(e){
-try{
-this.onreadystatechange=MochiKit.Async._nothing;
-}
-catch(e){
-}
-}
-var _290=null;
-try{
-_290=this.status;
-if(!_290&&MochiKit.Base.isNotEmpty(this.responseText)){
-_290=304;
-}
-}
-catch(e){
-}
-if(_290==200||_290==304){
-d.callback(this);
-}else{
-var err=new MochiKit.Async.XMLHttpRequestError(this,"Request failed");
-if(err.number){
-d.errback(err);
-}else{
-d.errback(err);
-}
-}
-}
-},_xhr_canceller:function(req){
-try{
-req.onreadystatechange=null;
-}
-catch(e){
-try{
-req.onreadystatechange=MochiKit.Async._nothing;
-}
-catch(e){
-}
-}
-req.abort();
-},sendXMLHttpRequest:function(req,_293){
-if(typeof (_293)=="undefined"||_293===null){
-_293="";
-}
-var m=MochiKit.Base;
-var self=MochiKit.Async;
-var d=new self.Deferred(m.partial(self._xhr_canceller,req));
-try{
-req.onreadystatechange=m.bind(self._xhr_onreadystatechange,req,d);
-req.send(_293);
-}
-catch(e){
-try{
-req.onreadystatechange=null;
-}
-catch(ignore){
-}
-d.errback(e);
-}
-return d;
-},doSimpleXMLHttpRequest:function(url){
-var self=MochiKit.Async;
-var req=self.getXMLHttpRequest();
-if(arguments.length>1){
-var m=MochiKit.Base;
-var qs=m.queryString.apply(null,m.extend(null,arguments,1));
-if(qs){
-url+="?"+qs;
-}
-}
-req.open("GET",url,true);
-return self.sendXMLHttpRequest(req);
-},loadJSONDoc:function(url){
-var self=MochiKit.Async;
-var d=self.doSimpleXMLHttpRequest.apply(self,arguments);
-d=d.addCallback(self.evalJSONRequest);
-return d;
-},wait:function(_296,_297){
-var d=new MochiKit.Async.Deferred();
-var m=MochiKit.Base;
-if(typeof (_297)!="undefined"){
-d.addCallback(function(){
-return _297;
-});
-}
-var _298=setTimeout(m.bind("callback",d),Math.floor(_296*1000));
-d.canceller=function(){
-try{
-clearTimeout(_298);
-}
-catch(e){
-}
-};
-return d;
-},callLater:function(_299,func){
-var m=MochiKit.Base;
-var _300=m.partial.apply(m,m.extend(null,arguments,1));
-return MochiKit.Async.wait(_299).addCallback(function(res){
-return _300();
-});
-}});
-MochiKit.Async.DeferredLock=function(){
-this.waiting=[];
-this.locked=false;
-this.id=this._nextId();
-};
-MochiKit.Async.DeferredLock.prototype={__class__:MochiKit.Async.DeferredLock,acquire:function(){
-d=new MochiKit.Async.Deferred();
-if(this.locked){
-this.waiting.push(d);
-}else{
-this.locked=true;
-d.callback(this);
-}
-return d;
-},release:function(){
-if(!this.locked){
-throw TypeError("Tried to release an unlocked DeferredLock");
-}
-this.locked=false;
-if(this.waiting.length>0){
-this.locked=true;
-this.waiting.shift().callback(this);
-}
-},_nextId:MochiKit.Base.counter(),repr:function(){
-var _301;
-if(this.locked){
-_301="locked, "+this.waiting.length+" waiting";
-}else{
-_301="unlocked";
-}
-return "DeferredLock("+this.id+", "+_301+")";
-},toString:MochiKit.Base.forwardCall("repr")};
-MochiKit.Async.DeferredList=function(list,_303,_304,_305,_306){
-this.list=list;
-this.resultList=new Array(this.list.length);
-this.chain=[];
-this.id=this._nextId();
-this.fired=-1;
-this.paused=0;
-this.results=[null,null];
-this.canceller=_306;
-this.silentlyCancelled=false;
-if(this.list.length===0&&!_303){
-this.callback(this.resultList);
-}
-this.finishedCount=0;
-this.fireOnOneCallback=_303;
-this.fireOnOneErrback=_304;
-this.consumeErrors=_305;
-var _307=0;
-MochiKit.Base.map(MochiKit.Base.bind(function(d){
-d.addCallback(MochiKit.Base.bind(this._cbDeferred,this),_307,true);
-d.addErrback(MochiKit.Base.bind(this._cbDeferred,this),_307,false);
-_307+=1;
-},this),this.list);
-};
-MochiKit.Base.update(MochiKit.Async.DeferredList.prototype,MochiKit.Async.Deferred.prototype);
-MochiKit.Base.update(MochiKit.Async.DeferredList.prototype,{_cbDeferred:function(_308,_309,_310){
-this.resultList[_308]=[_309,_310];
-this.finishedCount+=1;
-if(this.fired!==0){
-if(_309&&this.fireOnOneCallback){
-this.callback([_308,_310]);
-}else{
-if(!_309&&this.fireOnOneErrback){
-this.errback(_310);
-}else{
-if(this.finishedCount==this.list.length){
-this.callback(this.resultList);
-}
-}
-}
-}
-if(!_309&&this.consumeErrors){
-_310=null;
-}
-return _310;
-}});
-MochiKit.Async.gatherResults=function(_311){
-var d=new MochiKit.Async.DeferredList(_311,false,true,false);
-d.addCallback(function(_312){
-var ret=[];
-for(var i=0;i<_312.length;i++){
-ret.push(_312[i][1]);
-}
-return ret;
-});
-return d;
-};
-MochiKit.Async.maybeDeferred=function(func){
-var self=MochiKit.Async;
-var _313;
-try{
-var r=func.apply(null,MochiKit.Base.extend([],arguments,1));
-if(r instanceof self.Deferred){
-_313=r;
-}else{
-if(r instanceof Error){
-_313=self.fail(r);
-}else{
-_313=self.succeed(r);
-}
-}
-}
-catch(e){
-_313=self.fail(e);
-}
-return _313;
-};
-MochiKit.Async.EXPORT=["AlreadyCalledError","CancelledError","BrowserComplianceError","GenericError","XMLHttpRequestError","Deferred","succeed","fail","getXMLHttpRequest","doSimpleXMLHttpRequest","loadJSONDoc","wait","callLater","sendXMLHttpRequest","DeferredLock","DeferredList","gatherResults","maybeDeferred"];
-MochiKit.Async.EXPORT_OK=["evalJSONRequest"];
-MochiKit.Async.__new__=function(){
-var m=MochiKit.Base;
-var ne=m.partial(m._newNamedError,this);
-ne("AlreadyCalledError",function(_316){
-this.deferred=_316;
-});
-ne("CancelledError",function(_317){
-this.deferred=_317;
-});
-ne("BrowserComplianceError",function(msg){
-this.message=msg;
-});
-ne("GenericError",function(msg){
-this.message=msg;
-});
-ne("XMLHttpRequestError",function(req,msg){
-this.req=req;
-this.message=msg;
-try{
-this.number=req.status;
-}
-catch(e){
-}
-});
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-m.nameFunctions(this);
-};
-MochiKit.Async.__new__();
-MochiKit.Base._exportSymbols(this,MochiKit.Async);
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.DOM");
-dojo.require("MochiKit.Iter");
-}
-if(typeof (JSAN)!="undefined"){
-JSAN.use("MochiKit.Iter",[]);
-}
-try{
-if(typeof (MochiKit.Iter)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "MochiKit.DOM depends on MochiKit.Iter!";
-}
-if(typeof (MochiKit.DOM)=="undefined"){
-MochiKit.DOM={};
-}
-MochiKit.DOM.NAME="MochiKit.DOM";
-MochiKit.DOM.VERSION="1.3.1";
-MochiKit.DOM.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-MochiKit.DOM.toString=function(){
-return this.__repr__();
-};
-MochiKit.DOM.EXPORT=["formContents","currentWindow","currentDocument","withWindow","withDocument","registerDOMConverter","coerceToDOM","createDOM","createDOMFunc","getNodeAttribute","setNodeAttribute","updateNodeAttributes","appendChildNodes","replaceChildNodes","removeElement","swapDOM","BUTTON","TT","PRE","H1","H2","H3","BR","CANVAS","HR","LABEL","TEXTAREA","FORM","STRONG","SELECT","OPTION","OPTGROUP","LEGEND","FIELDSET","P","UL","OL","LI","TD","TR","THEAD","TBODY","TFOOT","TABLE","TH","INPUT","SPAN","A","DIV","IMG","getElement","$","computedStyle","getElementsByTagAndClassName","addToCallStack","addLoadEvent","focusOnLoad","setElementClass","toggleElementClass","addElementClass","removeElementClass","swapElementClass","hasElementClass","escapeHTML","toHTML","emitHTML","setDisplayForElement","hideElement","showElement","scrapeText","elementDimensions","elementPosition","setElementDimensions","setElementPosition","getViewportDimensions","setOpacity"];
-MochiKit.DOM.EXPORT_OK=["domConverters"];
-MochiKit.DOM.Dimensions=function(w,h){
-this.w=w;
-this.h=h;
-};
-MochiKit.DOM.Dimensions.prototype.repr=function(){
-var repr=MochiKit.Base.repr;
-return "{w: "+repr(this.w)+", h: "+repr(this.h)+"}";
-};
-MochiKit.DOM.Coordinates=function(x,y){
-this.x=x;
-this.y=y;
-};
-MochiKit.DOM.Coordinates.prototype.repr=function(){
-var repr=MochiKit.Base.repr;
-return "{x: "+repr(this.x)+", y: "+repr(this.y)+"}";
-};
-MochiKit.DOM.Coordinates.prototype.toString=function(){
-return this.repr();
-};
-MochiKit.Base.update(MochiKit.DOM,{setOpacity:function(elem,o){
-elem=MochiKit.DOM.getElement(elem);
-MochiKit.DOM.updateNodeAttributes(elem,{"style":{"opacity":o,"-moz-opacity":o,"-khtml-opacity":o,"filter":" alpha(opacity="+(o*100)+")"}});
-},getViewportDimensions:function(){
-var d=new MochiKit.DOM.Dimensions();
-var w=MochiKit.DOM._window;
-var b=MochiKit.DOM._document.body;
-if(w.innerWidth){
-d.w=w.innerWidth;
-d.h=w.innerHeight;
-}else{
-if(b.parentElement.clientWidth){
-d.w=b.parentElement.clientWidth;
-d.h=b.parentElement.clientHeight;
-}else{
-if(b&&b.clientWidth){
-d.w=b.clientWidth;
-d.h=b.clientHeight;
-}
-}
-}
-return d;
-},elementDimensions:function(elem){
-var self=MochiKit.DOM;
-if(typeof (elem.w)=="number"||typeof (elem.h)=="number"){
-return new self.Dimensions(elem.w||0,elem.h||0);
-}
-elem=self.getElement(elem);
-if(!elem){
-return undefined;
-}
-if(self.computedStyle(elem,"display")!="none"){
-return new self.Dimensions(elem.offsetWidth||0,elem.offsetHeight||0);
-}
-var s=elem.style;
-var _322=s.visibility;
-var _323=s.position;
-s.visibility="hidden";
-s.position="absolute";
-s.display="";
-var _324=elem.offsetWidth;
-var _325=elem.offsetHeight;
-s.display="none";
-s.position=_323;
-s.visibility=_322;
-return new self.Dimensions(_324,_325);
-},elementPosition:function(elem,_326){
-var self=MochiKit.DOM;
-elem=self.getElement(elem);
-if(!elem){
-return undefined;
-}
-var c=new self.Coordinates(0,0);
-if(elem.x&&elem.y){
-c.x+=elem.x||0;
-c.y+=elem.y||0;
-return c;
-}else{
-if(elem.parentNode===null||self.computedStyle(elem,"display")=="none"){
-return undefined;
-}
-}
-var box=null;
-var _329=null;
-var d=MochiKit.DOM._document;
-var de=d.documentElement;
-var b=d.body;
-if(elem.getBoundingClientRect){
-box=elem.getBoundingClientRect();
-c.x+=box.left+(de.scrollLeft||b.scrollLeft)-(de.clientLeft||b.clientLeft);
-c.y+=box.top+(de.scrollTop||b.scrollTop)-(de.clientTop||b.clientTop);
-}else{
-if(d.getBoxObjectFor){
-box=d.getBoxObjectFor(elem);
-c.x+=box.x;
-c.y+=box.y;
-}else{
-if(elem.offsetParent){
-c.x+=elem.offsetLeft;
-c.y+=elem.offsetTop;
-_329=elem.offsetParent;
-if(_329!=elem){
-while(_329){
-c.x+=_329.offsetLeft;
-c.y+=_329.offsetTop;
-_329=_329.offsetParent;
-}
-}
-var ua=navigator.userAgent.toLowerCase();
-if((typeof (opera)!="undefined"&&parseFloat(opera.version())<9)||(ua.indexOf("safari")!=-1&&self.computedStyle(elem,"position")=="absolute")){
-c.x-=b.offsetLeft;
-c.y-=b.offsetTop;
-}
-}
-}
-}
-if(typeof (_326)!="undefined"){
-_326=arguments.callee(_326);
-if(_326){
-c.x-=(_326.x||0);
-c.y-=(_326.y||0);
-}
-}
-if(elem.parentNode){
-_329=elem.parentNode;
-}else{
-_329=null;
-}
-while(_329&&_329.tagName!="BODY"&&_329.tagName!="HTML"){
-c.x-=_329.scrollLeft;
-c.y-=_329.scrollTop;
-if(_329.parentNode){
-_329=_329.parentNode;
-}else{
-_329=null;
-}
-}
-return c;
-},setElementDimensions:function(elem,_332,_333){
-elem=MochiKit.DOM.getElement(elem);
-if(typeof (_333)=="undefined"){
-_333="px";
-}
-MochiKit.DOM.updateNodeAttributes(elem,{"style":{"width":_332.w+_333,"height":_332.h+_333}});
-},setElementPosition:function(elem,_334,_335){
-elem=MochiKit.DOM.getElement(elem);
-if(typeof (_335)=="undefined"){
-_335="px";
-}
-MochiKit.DOM.updateNodeAttributes(elem,{"style":{"left":_334.x+_335,"top":_334.y+_335}});
-},currentWindow:function(){
-return MochiKit.DOM._window;
-},currentDocument:function(){
-return MochiKit.DOM._document;
-},withWindow:function(win,func){
-var self=MochiKit.DOM;
-var _337=self._document;
-var _338=self._win;
-var rval;
-try{
-self._window=win;
-self._document=win.document;
-rval=func();
-}
-catch(e){
-self._window=_338;
-self._document=_337;
-throw e;
-}
-self._window=_338;
-self._document=_337;
-return rval;
-},formContents:function(elem){
-var _339=[];
-var _340=[];
-var m=MochiKit.Base;
-var self=MochiKit.DOM;
-if(typeof (elem)=="undefined"||elem===null){
-elem=self._document;
-}else{
-elem=self.getElement(elem);
-}
-m.nodeWalk(elem,function(elem){
-var name=elem.name;
-if(m.isNotEmpty(name)){
-var _341=elem.nodeName;
-if(_341=="INPUT"&&(elem.type=="radio"||elem.type=="checkbox")&&!elem.checked){
-return null;
-}
-if(_341=="SELECT"){
-if(elem.selectedIndex>=0){
-var opt=elem.options[elem.selectedIndex];
-_339.push(name);
-_340.push((opt.value)?opt.value:opt.text);
-return null;
-}
-_339.push(name);
-_340.push("");
-return null;
-}
-if(_341=="FORM"||_341=="P"||_341=="SPAN"||_341=="DIV"){
-return elem.childNodes;
-}
-_339.push(name);
-_340.push(elem.value||"");
-return null;
-}
-return elem.childNodes;
-});
-return [_339,_340];
-},withDocument:function(doc,func){
-var self=MochiKit.DOM;
-var _344=self._document;
-var rval;
-try{
-self._document=doc;
-rval=func();
-}
-catch(e){
-self._document=_344;
-throw e;
-}
-self._document=_344;
-return rval;
-},registerDOMConverter:function(name,_345,wrap,_346){
-MochiKit.DOM.domConverters.register(name,_345,wrap,_346);
-},coerceToDOM:function(node,ctx){
-var im=MochiKit.Iter;
-var self=MochiKit.DOM;
-var iter=im.iter;
-var _350=im.repeat;
-var imap=im.imap;
-var _352=self.domConverters;
-var _353=self.coerceToDOM;
-var _354=MochiKit.Base.NotFound;
-while(true){
-if(typeof (node)=="undefined"||node===null){
-return null;
-}
-if(typeof (node.nodeType)!="undefined"&&node.nodeType>0){
-return node;
-}
-if(typeof (node)=="number"||typeof (node)=="boolean"){
-node=node.toString();
-}
-if(typeof (node)=="string"){
-return self._document.createTextNode(node);
-}
-if(typeof (node.toDOM)=="function"){
-node=node.toDOM(ctx);
-continue;
-}
-if(typeof (node)=="function"){
-node=node(ctx);
-continue;
-}
-var _355=null;
-try{
-_355=iter(node);
-}
-catch(e){
-}
-if(_355){
-return imap(_353,_355,_350(ctx));
-}
-try{
-node=_352.match(node,ctx);
-continue;
-}
-catch(e){
-if(e!=_354){
-throw e;
-}
-}
-return self._document.createTextNode(node.toString());
-}
-return undefined;
-},setNodeAttribute:function(node,attr,_357){
-var o={};
-o[attr]=_357;
-try{
-return MochiKit.DOM.updateNodeAttributes(node,o);
-}
-catch(e){
-}
-return null;
-},getNodeAttribute:function(node,attr){
-var self=MochiKit.DOM;
-var _358=self.attributeArray.renames[attr];
-node=self.getElement(node);
-try{
-if(_358){
-return node[_358];
-}
-return node.getAttribute(attr);
-}
-catch(e){
-}
-return null;
-},updateNodeAttributes:function(node,_359){
-var elem=node;
-var self=MochiKit.DOM;
-if(typeof (node)=="string"){
-elem=self.getElement(node);
-}
-if(_359){
-var _360=MochiKit.Base.updatetree;
-if(self.attributeArray.compliant){
-for(var k in _359){
-var v=_359[k];
-if(typeof (v)=="object"&&typeof (elem[k])=="object"){
-_360(elem[k],v);
-}else{
-if(k.substring(0,2)=="on"){
-if(typeof (v)=="string"){
-v=new Function(v);
-}
-elem[k]=v;
-}else{
-elem.setAttribute(k,v);
-}
-}
-}
-}else{
-var _361=self.attributeArray.renames;
-for(k in _359){
-v=_359[k];
-var _362=_361[k];
-if(k=="style"&&typeof (v)=="string"){
-elem.style.cssText=v;
-}else{
-if(typeof (_362)=="string"){
-elem[_362]=v;
-}else{
-if(typeof (elem[k])=="object"&&typeof (v)=="object"){
-_360(elem[k],v);
-}else{
-if(k.substring(0,2)=="on"){
-if(typeof (v)=="string"){
-v=new Function(v);
-}
-elem[k]=v;
-}else{
-elem.setAttribute(k,v);
-}
-}
-}
-}
-}
-}
-}
-return elem;
-},appendChildNodes:function(node){
-var elem=node;
-var self=MochiKit.DOM;
-if(typeof (node)=="string"){
-elem=self.getElement(node);
-}
-var _363=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)];
-var _364=MochiKit.Base.concat;
-while(_363.length){
-var n=_363.shift();
-if(typeof (n)=="undefined"||n===null){
-}else{
-if(typeof (n.nodeType)=="number"){
-elem.appendChild(n);
-}else{
-_363=_364(n,_363);
-}
-}
-}
-return elem;
-},replaceChildNodes:function(node){
-var elem=node;
-var self=MochiKit.DOM;
-if(typeof (node)=="string"){
-elem=self.getElement(node);
-arguments[0]=elem;
-}
-var _365;
-while((_365=elem.firstChild)){
-elem.removeChild(_365);
-}
-if(arguments.length<2){
-return elem;
-}else{
-return self.appendChildNodes.apply(this,arguments);
-}
-},createDOM:function(name,_366){
-var elem;
-var self=MochiKit.DOM;
-var m=MochiKit.Base;
-if(typeof (_366)=="string"||typeof (_366)=="number"){
-var args=m.extend([name,null],arguments,1);
-return arguments.callee.apply(this,args);
-}
-if(typeof (name)=="string"){
-if(_366&&"name" in _366&&!self.attributeArray.compliant){
-name=("<"+name+" name=\""+self.escapeHTML(_366.name)+"\">");
-}
-elem=self._document.createElement(name);
-}else{
-elem=name;
-}
-if(_366){
-self.updateNodeAttributes(elem,_366);
-}
-if(arguments.length<=2){
-return elem;
-}else{
-var args=m.extend([elem],arguments,2);
-return self.appendChildNodes.apply(this,args);
-}
-},createDOMFunc:function(){
-var m=MochiKit.Base;
-return m.partial.apply(this,m.extend([MochiKit.DOM.createDOM],arguments));
-},swapDOM:function(dest,src){
-var self=MochiKit.DOM;
-dest=self.getElement(dest);
-var _369=dest.parentNode;
-if(src){
-src=self.getElement(src);
-_369.replaceChild(src,dest);
-}else{
-_369.removeChild(dest);
-}
-return src;
-},getElement:function(id){
-var self=MochiKit.DOM;
-if(arguments.length==1){
-return ((typeof (id)=="string")?self._document.getElementById(id):id);
-}else{
-return MochiKit.Base.map(self.getElement,arguments);
-}
-},computedStyle:function(_371,_372,_373){
-if(arguments.length==2){
-_373=_372;
-}
-var self=MochiKit.DOM;
-var el=self.getElement(_371);
-var _375=self._document;
-if(!el||el==_375){
-return undefined;
-}
-if(el.currentStyle){
-return el.currentStyle[_372];
-}
-if(typeof (_375.defaultView)=="undefined"){
-return undefined;
-}
-if(_375.defaultView===null){
-return undefined;
-}
-var _376=_375.defaultView.getComputedStyle(el,null);
-if(typeof (_376)=="undefined"||_376===null){
-return undefined;
-}
-return _376.getPropertyValue(_373);
-},getElementsByTagAndClassName:function(_377,_378,_379){
-var self=MochiKit.DOM;
-if(typeof (_377)=="undefined"||_377===null){
-_377="*";
-}
-if(typeof (_379)=="undefined"||_379===null){
-_379=self._document;
-}
-_379=self.getElement(_379);
-var _380=(_379.getElementsByTagName(_377)||self._document.all);
-if(typeof (_378)=="undefined"||_378===null){
-return MochiKit.Base.extend(null,_380);
-}
-var _381=[];
-for(var i=0;i<_380.length;i++){
-var _382=_380[i];
-var _383=_382.className.split(" ");
-for(var j=0;j<_383.length;j++){
-if(_383[j]==_378){
-_381.push(_382);
-break;
-}
-}
-}
-return _381;
-},_newCallStack:function(path,once){
-var rval=function(){
-var _386=arguments.callee.callStack;
-for(var i=0;i<_386.length;i++){
-if(_386[i].apply(this,arguments)===false){
-break;
-}
-}
-if(once){
-try{
-this[path]=null;
-}
-catch(e){
-}
-}
-};
-rval.callStack=[];
-return rval;
-},addToCallStack:function(_387,path,func,once){
-var self=MochiKit.DOM;
-var _388=_387[path];
-var _389=_388;
-if(!(typeof (_388)=="function"&&typeof (_388.callStack)=="object"&&_388.callStack!==null)){
-_389=self._newCallStack(path,once);
-if(typeof (_388)=="function"){
-_389.callStack.push(_388);
-}
-_387[path]=_389;
-}
-_389.callStack.push(func);
-},addLoadEvent:function(func){
-var self=MochiKit.DOM;
-self.addToCallStack(self._window,"onload",func,true);
-},focusOnLoad:function(_390){
-var self=MochiKit.DOM;
-self.addLoadEvent(function(){
-_390=self.getElement(_390);
-if(_390){
-_390.focus();
-}
-});
-},setElementClass:function(_391,_392){
-var self=MochiKit.DOM;
-var obj=self.getElement(_391);
-if(self.attributeArray.compliant){
-obj.setAttribute("class",_392);
-}else{
-obj.setAttribute("className",_392);
-}
-},toggleElementClass:function(_393){
-var self=MochiKit.DOM;
-for(var i=1;i<arguments.length;i++){
-var obj=self.getElement(arguments[i]);
-if(!self.addElementClass(obj,_393)){
-self.removeElementClass(obj,_393);
-}
-}
-},addElementClass:function(_394,_395){
-var self=MochiKit.DOM;
-var obj=self.getElement(_394);
-var cls=obj.className;
-if(cls.length===0){
-self.setElementClass(obj,_395);
-return true;
-}
-if(cls==_395){
-return false;
-}
-var _397=obj.className.split(" ");
-for(var i=0;i<_397.length;i++){
-if(_397[i]==_395){
-return false;
-}
-}
-self.setElementClass(obj,cls+" "+_395);
-return true;
-},removeElementClass:function(_398,_399){
-var self=MochiKit.DOM;
-var obj=self.getElement(_398);
-var cls=obj.className;
-if(cls.length===0){
-return false;
-}
-if(cls==_399){
-self.setElementClass(obj,"");
-return true;
-}
-var _400=obj.className.split(" ");
-for(var i=0;i<_400.length;i++){
-if(_400[i]==_399){
-_400.splice(i,1);
-self.setElementClass(obj,_400.join(" "));
-return true;
-}
-}
-return false;
-},swapElementClass:function(_401,_402,_403){
-var obj=MochiKit.DOM.getElement(_401);
-var res=MochiKit.DOM.removeElementClass(obj,_402);
-if(res){
-MochiKit.DOM.addElementClass(obj,_403);
-}
-return res;
-},hasElementClass:function(_404,_405){
-var obj=MochiKit.DOM.getElement(_404);
-var _406=obj.className.split(" ");
-for(var i=1;i<arguments.length;i++){
-var good=false;
-for(var j=0;j<_406.length;j++){
-if(_406[j]==arguments[i]){
-good=true;
-break;
-}
-}
-if(!good){
-return false;
-}
-}
-return true;
-},escapeHTML:function(s){
-return s.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">");
-},toHTML:function(dom){
-return MochiKit.DOM.emitHTML(dom).join("");
-},emitHTML:function(dom,lst){
-if(typeof (lst)=="undefined"||lst===null){
-lst=[];
-}
-var _409=[dom];
-var self=MochiKit.DOM;
-var _410=self.escapeHTML;
-var _411=self.attributeArray;
-while(_409.length){
-dom=_409.pop();
-if(typeof (dom)=="string"){
-lst.push(dom);
-}else{
-if(dom.nodeType==1){
-lst.push("<"+dom.nodeName.toLowerCase());
-var _412=[];
-var _413=_411(dom);
-for(var i=0;i<_413.length;i++){
-var a=_413[i];
-_412.push([" ",a.name,"=\"",_410(a.value),"\""]);
-}
-_412.sort();
-for(i=0;i<_412.length;i++){
-var _414=_412[i];
-for(var j=0;j<_414.length;j++){
-lst.push(_414[j]);
-}
-}
-if(dom.hasChildNodes()){
-lst.push(">");
-_409.push("</"+dom.nodeName.toLowerCase()+">");
-var _415=dom.childNodes;
-for(i=_415.length-1;i>=0;i--){
-_409.push(_415[i]);
-}
-}else{
-lst.push("/>");
-}
-}else{
-if(dom.nodeType==3){
-lst.push(_410(dom.nodeValue));
-}
-}
-}
-}
-return lst;
-},setDisplayForElement:function(_416,_417){
-var m=MochiKit.Base;
-var _418=m.extend(null,arguments,1);
-MochiKit.Iter.forEach(m.filter(null,m.map(MochiKit.DOM.getElement,_418)),function(_417){
-_417.style.display=_416;
-});
-},scrapeText:function(node,_419){
-var rval=[];
-(function(node){
-var cn=node.childNodes;
-if(cn){
-for(var i=0;i<cn.length;i++){
-arguments.callee.call(this,cn[i]);
-}
-}
-var _421=node.nodeValue;
-if(typeof (_421)=="string"){
-rval.push(_421);
-}
-})(MochiKit.DOM.getElement(node));
-if(_419){
-return rval;
-}else{
-return rval.join("");
-}
-},__new__:function(win){
-var m=MochiKit.Base;
-this._document=document;
-this._window=win;
-this.domConverters=new m.AdapterRegistry();
-var _422=this._document.createElement("span");
-var _423;
-if(_422&&_422.attributes&&_422.attributes.length>0){
-var _424=m.filter;
-_423=function(node){
-return _424(_423.ignoreAttrFilter,node.attributes);
-};
-_423.ignoreAttr={};
-MochiKit.Iter.forEach(_422.attributes,function(a){
-_423.ignoreAttr[a.name]=a.value;
-});
-_423.ignoreAttrFilter=function(a){
-return (_423.ignoreAttr[a.name]!=a.value);
-};
-_423.compliant=false;
-_423.renames={"class":"className","checked":"defaultChecked","usemap":"useMap","for":"htmlFor"};
-}else{
-_423=function(node){
-return node.attributes;
-};
-_423.compliant=true;
-_423.renames={};
-}
-this.attributeArray=_423;
-var _425=this.createDOMFunc;
-this.UL=_425("ul");
-this.OL=_425("ol");
-this.LI=_425("li");
-this.TD=_425("td");
-this.TR=_425("tr");
-this.TBODY=_425("tbody");
-this.THEAD=_425("thead");
-this.TFOOT=_425("tfoot");
-this.TABLE=_425("table");
-this.TH=_425("th");
-this.INPUT=_425("input");
-this.SPAN=_425("span");
-this.A=_425("a");
-this.DIV=_425("div");
-this.IMG=_425("img");
-this.BUTTON=_425("button");
-this.TT=_425("tt");
-this.PRE=_425("pre");
-this.H1=_425("h1");
-this.H2=_425("h2");
-this.H3=_425("h3");
-this.BR=_425("br");
-this.HR=_425("hr");
-this.LABEL=_425("label");
-this.TEXTAREA=_425("textarea");
-this.FORM=_425("form");
-this.P=_425("p");
-this.SELECT=_425("select");
-this.OPTION=_425("option");
-this.OPTGROUP=_425("optgroup");
-this.LEGEND=_425("legend");
-this.FIELDSET=_425("fieldset");
-this.STRONG=_425("strong");
-this.CANVAS=_425("canvas");
-this.hideElement=m.partial(this.setDisplayForElement,"none");
-this.showElement=m.partial(this.setDisplayForElement,"block");
-this.removeElement=this.swapDOM;
-this.$=this.getElement;
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-m.nameFunctions(this);
-}});
-MochiKit.DOM.__new__(((typeof (window)=="undefined")?this:window));
-if(!MochiKit.__compat__){
-withWindow=MochiKit.DOM.withWindow;
-withDocument=MochiKit.DOM.withDocument;
-}
-MochiKit.Base._exportSymbols(this,MochiKit.DOM);
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.LoggingPane");
-dojo.require("MochiKit.Logging");
-dojo.require("MochiKit.Base");
-}
-if(typeof (JSAN)!="undefined"){
-JSAN.use("MochiKit.Logging",[]);
-JSAN.use("MochiKit.Base",[]);
-}
-try{
-if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.Logging)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "MochiKit.LoggingPane depends on MochiKit.Base and MochiKit.Logging!";
-}
-if(typeof (MochiKit.LoggingPane)=="undefined"){
-MochiKit.LoggingPane={};
-}
-MochiKit.LoggingPane.NAME="MochiKit.LoggingPane";
-MochiKit.LoggingPane.VERSION="1.3.1";
-MochiKit.LoggingPane.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-MochiKit.LoggingPane.toString=function(){
-return this.__repr__();
-};
-MochiKit.LoggingPane.createLoggingPane=function(_426){
-var m=MochiKit.LoggingPane;
-_426=!(!_426);
-if(m._loggingPane&&m._loggingPane.inline!=_426){
-m._loggingPane.closePane();
-m._loggingPane=null;
-}
-if(!m._loggingPane||m._loggingPane.closed){
-m._loggingPane=new m.LoggingPane(_426,MochiKit.Logging.logger);
-}
-return m._loggingPane;
-};
-MochiKit.LoggingPane.LoggingPane=function(_427,_428){
-if(typeof (_428)=="undefined"||_428===null){
-_428=MochiKit.Logging.logger;
-}
-this.logger=_428;
-var _429=MochiKit.Base.update;
-var _430=MochiKit.Base.updatetree;
-var bind=MochiKit.Base.bind;
-var _431=MochiKit.Base.clone;
-var win=window;
-var uid="_MochiKit_LoggingPane";
-if(typeof (MochiKit.DOM)!="undefined"){
-win=MochiKit.DOM.currentWindow();
-}
-if(!_427){
-var url=win.location.href.split("?")[0].replace(/[:\/.><&]/g,"_");
-var name=uid+"_"+url;
-var nwin=win.open("",name,"dependent,resizable,height=200");
-if(!nwin){
-alert("Not able to open debugging window due to pop-up blocking.");
-return undefined;
-}
-nwin.document.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" "+"\"http://www.w3.org/TR/html4/loose.dtd\">"+"<html><head><title>[MochiKit.LoggingPane]</title></head>"+"<body></body></html>");
-nwin.document.close();
-nwin.document.title+=" "+win.document.title;
-win=nwin;
-}
-var doc=win.document;
-this.doc=doc;
-var _434=doc.getElementById(uid);
-var _435=!!_434;
-if(_434&&typeof (_434.loggingPane)!="undefined"){
-_434.loggingPane.logger=this.logger;
-_434.loggingPane.buildAndApplyFilter();
-return _434.loggingPane;
-}
-if(_435){
-var _436;
-while((_436=_434.firstChild)){
-_434.removeChild(_436);
-}
-}else{
-_434=doc.createElement("div");
-_434.id=uid;
-}
-_434.loggingPane=this;
-var _437=doc.createElement("input");
-var _438=doc.createElement("input");
-var _439=doc.createElement("button");
-var _440=doc.createElement("button");
-var _441=doc.createElement("button");
-var _442=doc.createElement("button");
-var _443=doc.createElement("div");
-var _444=doc.createElement("div");
-var _445=uid+"_Listener";
-this.colorTable=_431(this.colorTable);
-var _446=[];
-var _447=null;
-var _448=function(msg){
-var _449=msg.level;
-if(typeof (_449)=="number"){
-_449=MochiKit.Logging.LogLevel[_449];
-}
-return _449;
-};
-var _450=function(msg){
-return msg.info.join(" ");
-};
-var _451=bind(function(msg){
-var _452=_448(msg);
-var text=_450(msg);
-var c=this.colorTable[_452];
-var p=doc.createElement("span");
-p.className="MochiKit-LogMessage MochiKit-LogLevel-"+_452;
-p.style.cssText="margin: 0px; white-space: -moz-pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; wrap-option: emergency; color: "+c;
-p.appendChild(doc.createTextNode(_452+": "+text));
-_444.appendChild(p);
-_444.appendChild(doc.createElement("br"));
-if(_443.offsetHeight>_443.scrollHeight){
-_443.scrollTop=0;
-}else{
-_443.scrollTop=_443.scrollHeight;
-}
-},this);
-var _454=function(msg){
-_446[_446.length]=msg;
-_451(msg);
-};
-var _455=function(){
-var _456,infore;
-try{
-_456=new RegExp(_437.value);
-infore=new RegExp(_438.value);
-}
-catch(e){
-logDebug("Error in filter regex: "+e.message);
-return null;
-}
-return function(msg){
-return (_456.test(_448(msg))&&infore.test(_450(msg)));
-};
-};
-var _457=function(){
-while(_444.firstChild){
-_444.removeChild(_444.firstChild);
-}
-};
-var _458=function(){
-_446=[];
-_457();
-};
-var _459=bind(function(){
-if(this.closed){
-return;
-}
-this.closed=true;
-if(MochiKit.LoggingPane._loggingPane==this){
-MochiKit.LoggingPane._loggingPane=null;
-}
-this.logger.removeListener(_445);
-_434.loggingPane=null;
-if(_427){
-_434.parentNode.removeChild(_434);
-}else{
-this.win.close();
-}
-},this);
-var _460=function(){
-_457();
-for(var i=0;i<_446.length;i++){
-var msg=_446[i];
-if(_447===null||_447(msg)){
-_451(msg);
-}
-}
-};
-this.buildAndApplyFilter=function(){
-_447=_455();
-_460();
-this.logger.removeListener(_445);
-this.logger.addListener(_445,_447,_454);
-};
-var _461=bind(function(){
-_446=this.logger.getMessages();
-_460();
-},this);
-var _462=bind(function(_463){
-_463=_463||window.event;
-key=_463.which||_463.keyCode;
-if(key==13){
-this.buildAndApplyFilter();
-}
-},this);
-var _464="display: block; z-index: 1000; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: "+this.logFont;
-if(_427){
-_464+="; height: 10em; border-top: 2px solid black";
-}else{
-_464+="; height: 100%;";
-}
-_434.style.cssText=_464;
-if(!_435){
-doc.body.appendChild(_434);
-}
-_464={"cssText":"width: 33%; display: inline; font: "+this.logFont};
-_430(_437,{"value":"FATAL|ERROR|WARNING|INFO|DEBUG","onkeypress":_462,"style":_464});
-_434.appendChild(_437);
-_430(_438,{"value":".*","onkeypress":_462,"style":_464});
-_434.appendChild(_438);
-_464="width: 8%; display:inline; font: "+this.logFont;
-_439.appendChild(doc.createTextNode("Filter"));
-_439.onclick=bind("buildAndApplyFilter",this);
-_439.style.cssText=_464;
-_434.appendChild(_439);
-_440.appendChild(doc.createTextNode("Load"));
-_440.onclick=_461;
-_440.style.cssText=_464;
-_434.appendChild(_440);
-_441.appendChild(doc.createTextNode("Clear"));
-_441.onclick=_458;
-_441.style.cssText=_464;
-_434.appendChild(_441);
-_442.appendChild(doc.createTextNode("Close"));
-_442.onclick=_459;
-_442.style.cssText=_464;
-_434.appendChild(_442);
-_443.style.cssText="overflow: auto; width: 100%";
-_444.style.cssText="width: 100%; height: "+(_427?"8em":"100%");
-_443.appendChild(_444);
-_434.appendChild(_443);
-this.buildAndApplyFilter();
-_461();
-if(_427){
-this.win=undefined;
-}else{
-this.win=win;
-}
-this.inline=_427;
-this.closePane=_459;
-this.closed=false;
-return this;
-};
-MochiKit.LoggingPane.LoggingPane.prototype={"logFont":"8pt Verdana,sans-serif","colorTable":{"ERROR":"red","FATAL":"darkred","WARNING":"blue","INFO":"black","DEBUG":"green"}};
-MochiKit.LoggingPane.EXPORT_OK=["LoggingPane"];
-MochiKit.LoggingPane.EXPORT=["createLoggingPane"];
-MochiKit.LoggingPane.__new__=function(){
-this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)};
-MochiKit.Base.nameFunctions(this);
-MochiKit.LoggingPane._loggingPane=null;
-};
-MochiKit.LoggingPane.__new__();
-MochiKit.Base._exportSymbols(this,MochiKit.LoggingPane);
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.Color");
-dojo.require("MochiKit.Base");
-}
-if(typeof (JSAN)!="undefined"){
-JSAN.use("MochiKit.Base",[]);
-}
-try{
-if(typeof (MochiKit.Base)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "MochiKit.Color depends on MochiKit.Base";
-}
-if(typeof (MochiKit.Color)=="undefined"){
-MochiKit.Color={};
-}
-MochiKit.Color.NAME="MochiKit.Color";
-MochiKit.Color.VERSION="1.3.1";
-MochiKit.Color.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-MochiKit.Color.toString=function(){
-return this.__repr__();
-};
-MochiKit.Color.Color=function(red,_466,blue,_468){
-if(typeof (_468)=="undefined"||_468===null){
-_468=1;
-}
-this.rgb={r:red,g:_466,b:blue,a:_468};
-};
-MochiKit.Color.Color.prototype={__class__:MochiKit.Color.Color,colorWithAlpha:function(_469){
-var rgb=this.rgb;
-var m=MochiKit.Color;
-return m.Color.fromRGB(rgb.r,rgb.g,rgb.b,_469);
-},colorWithHue:function(hue){
-var hsl=this.asHSL();
-hsl.h=hue;
-var m=MochiKit.Color;
-return m.Color.fromHSL(hsl);
-},colorWithSaturation:function(_473){
-var hsl=this.asHSL();
-hsl.s=_473;
-var m=MochiKit.Color;
-return m.Color.fromHSL(hsl);
-},colorWithLightness:function(_474){
-var hsl=this.asHSL();
-hsl.l=_474;
-var m=MochiKit.Color;
-return m.Color.fromHSL(hsl);
-},darkerColorWithLevel:function(_475){
-var hsl=this.asHSL();
-hsl.l=Math.max(hsl.l-_475,0);
-var m=MochiKit.Color;
-return m.Color.fromHSL(hsl);
-},lighterColorWithLevel:function(_476){
-var hsl=this.asHSL();
-hsl.l=Math.min(hsl.l+_476,1);
-var m=MochiKit.Color;
-return m.Color.fromHSL(hsl);
-},blendedColor:function(_477,_478){
-if(typeof (_478)=="undefined"||_478===null){
-_478=0.5;
-}
-var sf=1-_478;
-var s=this.rgb;
-var d=_477.rgb;
-var df=_478;
-return MochiKit.Color.Color.fromRGB((s.r*sf)+(d.r*df),(s.g*sf)+(d.g*df),(s.b*sf)+(d.b*df),(s.a*sf)+(d.a*df));
-},compareRGB:function(_481){
-var a=this.asRGB();
-var b=_481.asRGB();
-return MochiKit.Base.compare([a.r,a.g,a.b,a.a],[b.r,b.g,b.b,b.a]);
-},isLight:function(){
-return this.asHSL().b>0.5;
-},isDark:function(){
-return (!this.isLight());
-},toHSLString:function(){
-var c=this.asHSL();
-var ccc=MochiKit.Color.clampColorComponent;
-var rval=this._hslString;
-if(!rval){
-var mid=(ccc(c.h,360).toFixed(0)+","+ccc(c.s,100).toPrecision(4)+"%"+","+ccc(c.l,100).toPrecision(4)+"%");
-var a=c.a;
-if(a>=1){
-a=1;
-rval="hsl("+mid+")";
-}else{
-if(a<=0){
-a=0;
-}
-rval="hsla("+mid+","+a+")";
-}
-this._hslString=rval;
-}
-return rval;
-},toRGBString:function(){
-var c=this.rgb;
-var ccc=MochiKit.Color.clampColorComponent;
-var rval=this._rgbString;
-if(!rval){
-var mid=(ccc(c.r,255).toFixed(0)+","+ccc(c.g,255).toFixed(0)+","+ccc(c.b,255).toFixed(0));
-if(c.a!=1){
-rval="rgba("+mid+","+c.a+")";
-}else{
-rval="rgb("+mid+")";
-}
-this._rgbString=rval;
-}
-return rval;
-},asRGB:function(){
-return MochiKit.Base.clone(this.rgb);
-},toHexString:function(){
-var m=MochiKit.Color;
-var c=this.rgb;
-var ccc=MochiKit.Color.clampColorComponent;
-var rval=this._hexString;
-if(!rval){
-rval=("#"+m.toColorPart(ccc(c.r,255))+m.toColorPart(ccc(c.g,255))+m.toColorPart(ccc(c.b,255)));
-this._hexString=rval;
-}
-return rval;
-},asHSV:function(){
-var hsv=this.hsv;
-var c=this.rgb;
-if(typeof (hsv)=="undefined"||hsv===null){
-hsv=MochiKit.Color.rgbToHSV(this.rgb);
-this.hsv=hsv;
-}
-return MochiKit.Base.clone(hsv);
-},asHSL:function(){
-var hsl=this.hsl;
-var c=this.rgb;
-if(typeof (hsl)=="undefined"||hsl===null){
-hsl=MochiKit.Color.rgbToHSL(this.rgb);
-this.hsl=hsl;
-}
-return MochiKit.Base.clone(hsl);
-},toString:function(){
-return this.toRGBString();
-},repr:function(){
-var c=this.rgb;
-var col=[c.r,c.g,c.b,c.a];
-return this.__class__.NAME+"("+col.join(", ")+")";
-}};
-MochiKit.Base.update(MochiKit.Color.Color,{fromRGB:function(red,_486,blue,_487){
-var _488=MochiKit.Color.Color;
-if(arguments.length==1){
-var rgb=red;
-red=rgb.r;
-_486=rgb.g;
-blue=rgb.b;
-if(typeof (rgb.a)=="undefined"){
-_487=undefined;
-}else{
-_487=rgb.a;
-}
-}
-return new _488(red,_486,blue,_487);
-},fromHSL:function(hue,_489,_490,_491){
-var m=MochiKit.Color;
-return m.Color.fromRGB(m.hslToRGB.apply(m,arguments));
-},fromHSV:function(hue,_492,_493,_494){
-var m=MochiKit.Color;
-return m.Color.fromRGB(m.hsvToRGB.apply(m,arguments));
-},fromName:function(name){
-var _495=MochiKit.Color.Color;
-if(name.charAt(0)=="\""){
-name=name.substr(1,name.length-2);
-}
-var _496=_495._namedColors[name.toLowerCase()];
-if(typeof (_496)=="string"){
-return _495.fromHexString(_496);
-}else{
-if(name=="transparent"){
-return _495.transparentColor();
-}
-}
-return null;
-},fromString:function(_497){
-var self=MochiKit.Color.Color;
-var _498=_497.substr(0,3);
-if(_498=="rgb"){
-return self.fromRGBString(_497);
-}else{
-if(_498=="hsl"){
-return self.fromHSLString(_497);
-}else{
-if(_497.charAt(0)=="#"){
-return self.fromHexString(_497);
-}
-}
-}
-return self.fromName(_497);
-},fromHexString:function(_499){
-if(_499.charAt(0)=="#"){
-_499=_499.substring(1);
-}
-var _500=[];
-var i,hex;
-if(_499.length==3){
-for(i=0;i<3;i++){
-hex=_499.substr(i,1);
-_500.push(parseInt(hex+hex,16)/255);
-}
-}else{
-for(i=0;i<6;i+=2){
-hex=_499.substr(i,2);
-_500.push(parseInt(hex,16)/255);
-}
-}
-var _501=MochiKit.Color.Color;
-return _501.fromRGB.apply(_501,_500);
-},_fromColorString:function(pre,_503,_504,_505){
-if(_505.indexOf(pre)===0){
-_505=_505.substring(_505.indexOf("(",3)+1,_505.length-1);
-}
-var _506=_505.split(/\s*,\s*/);
-var _507=[];
-for(var i=0;i<_506.length;i++){
-var c=_506[i];
-var val;
-var _508=c.substring(c.length-3);
-if(c.charAt(c.length-1)=="%"){
-val=0.01*parseFloat(c.substring(0,c.length-1));
-}else{
-if(_508=="deg"){
-val=parseFloat(c)/360;
-}else{
-if(_508=="rad"){
-val=parseFloat(c)/(Math.PI*2);
-}else{
-val=_504[i]*parseFloat(c);
-}
-}
-}
-_507.push(val);
-}
-return this[_503].apply(this,_507);
-},fromComputedStyle:function(elem,_509,_510){
-var d=MochiKit.DOM;
-var cls=MochiKit.Color.Color;
-for(elem=d.getElement(elem);elem;elem=elem.parentNode){
-var _511=d.computedStyle.apply(d,arguments);
-if(!_511){
-continue;
-}
-var _512=cls.fromString(_511);
-if(!_512){
-break;
-}
-if(_512.asRGB().a>0){
-return _512;
-}
-}
-return null;
-},fromBackground:function(elem){
-var cls=MochiKit.Color.Color;
-return cls.fromComputedStyle(elem,"backgroundColor","background-color")||cls.whiteColor();
-},fromText:function(elem){
-var cls=MochiKit.Color.Color;
-return cls.fromComputedStyle(elem,"color","color")||cls.blackColor();
-},namedColors:function(){
-return MochiKit.Base.clone(MochiKit.Color.Color._namedColors);
-}});
-MochiKit.Base.update(MochiKit.Color,{clampColorComponent:function(v,_513){
-v*=_513;
-if(v<0){
-return 0;
-}else{
-if(v>_513){
-return _513;
-}else{
-return v;
-}
-}
-},_hslValue:function(n1,n2,hue){
-if(hue>6){
-hue-=6;
-}else{
-if(hue<0){
-hue+=6;
-}
-}
-var val;
-if(hue<1){
-val=n1+(n2-n1)*hue;
-}else{
-if(hue<3){
-val=n2;
-}else{
-if(hue<4){
-val=n1+(n2-n1)*(4-hue);
-}else{
-val=n1;
-}
-}
-}
-return val;
-},hsvToRGB:function(hue,_516,_517,_518){
-if(arguments.length==1){
-var hsv=hue;
-hue=hsv.h;
-_516=hsv.s;
-_517=hsv.v;
-_518=hsv.a;
-}
-var red;
-var _519;
-var blue;
-if(_516===0){
-red=0;
-_519=0;
-blue=0;
-}else{
-var i=Math.floor(hue*6);
-var f=(hue*6)-i;
-var p=_517*(1-_516);
-var q=_517*(1-(_516*f));
-var t=_517*(1-(_516*(1-f)));
-switch(i){
-case 1:
-red=q;
-_519=_517;
-blue=p;
-break;
-case 2:
-red=p;
-_519=_517;
-blue=t;
-break;
-case 3:
-red=p;
-_519=q;
-blue=_517;
-break;
-case 4:
-red=t;
-_519=p;
-blue=_517;
-break;
-case 5:
-red=_517;
-_519=p;
-blue=q;
-break;
-case 6:
-case 0:
-red=_517;
-_519=t;
-blue=p;
-break;
-}
-}
-return {r:red,g:_519,b:blue,a:_518};
-},hslToRGB:function(hue,_521,_522,_523){
-if(arguments.length==1){
-var hsl=hue;
-hue=hsl.h;
-_521=hsl.s;
-_522=hsl.l;
-_523=hsl.a;
-}
-var red;
-var _524;
-var blue;
-if(_521===0){
-red=_522;
-_524=_522;
-blue=_522;
-}else{
-var m2;
-if(_522<=0.5){
-m2=_522*(1+_521);
-}else{
-m2=_522+_521-(_522*_521);
-}
-var m1=(2*_522)-m2;
-var f=MochiKit.Color._hslValue;
-var h6=hue*6;
-red=f(m1,m2,h6+2);
-_524=f(m1,m2,h6);
-blue=f(m1,m2,h6-2);
-}
-return {r:red,g:_524,b:blue,a:_523};
-},rgbToHSV:function(red,_528,blue,_529){
-if(arguments.length==1){
-var rgb=red;
-red=rgb.r;
-_528=rgb.g;
-blue=rgb.b;
-_529=rgb.a;
-}
-var max=Math.max(Math.max(red,_528),blue);
-var min=Math.min(Math.min(red,_528),blue);
-var hue;
-var _532;
-var _533=max;
-if(min==max){
-hue=0;
-_532=0;
-}else{
-var _534=(max-min);
-_532=_534/max;
-if(red==max){
-hue=(_528-blue)/_534;
-}else{
-if(_528==max){
-hue=2+((blue-red)/_534);
-}else{
-hue=4+((red-_528)/_534);
-}
-}
-hue/=6;
-if(hue<0){
-hue+=1;
-}
-if(hue>1){
-hue-=1;
-}
-}
-return {h:hue,s:_532,v:_533,a:_529};
-},rgbToHSL:function(red,_535,blue,_536){
-if(arguments.length==1){
-var rgb=red;
-red=rgb.r;
-_535=rgb.g;
-blue=rgb.b;
-_536=rgb.a;
-}
-var max=Math.max(red,Math.max(_535,blue));
-var min=Math.min(red,Math.min(_535,blue));
-var hue;
-var _537;
-var _538=(max+min)/2;
-var _539=max-min;
-if(_539===0){
-hue=0;
-_537=0;
-}else{
-if(_538<=0.5){
-_537=_539/(max+min);
-}else{
-_537=_539/(2-max-min);
-}
-if(red==max){
-hue=(_535-blue)/_539;
-}else{
-if(_535==max){
-hue=2+((blue-red)/_539);
-}else{
-hue=4+((red-_535)/_539);
-}
-}
-hue/=6;
-if(hue<0){
-hue+=1;
-}
-if(hue>1){
-hue-=1;
-}
-}
-return {h:hue,s:_537,l:_538,a:_536};
-},toColorPart:function(num){
-num=Math.round(num);
-var _540=num.toString(16);
-if(num<16){
-return "0"+_540;
-}
-return _540;
-},__new__:function(){
-var m=MochiKit.Base;
-this.Color.fromRGBString=m.bind(this.Color._fromColorString,this.Color,"rgb","fromRGB",[1/255,1/255,1/255,1]);
-this.Color.fromHSLString=m.bind(this.Color._fromColorString,this.Color,"hsl","fromHSL",[1/360,0.01,0.01,1]);
-var _541=1/3;
-var _542={black:[0,0,0],blue:[0,0,1],brown:[0.6,0.4,0.2],cyan:[0,1,1],darkGray:[_541,_541,_541],gray:[0.5,0.5,0.5],green:[0,1,0],lightGray:[2*_541,2*_541,2*_541],magenta:[1,0,1],orange:[1,0.5,0],purple:[0.5,0,0.5],red:[1,0,0],transparent:[0,0,0,0],white:[1,1,1],yellow:[1,1,0]};
-var _543=function(name,r,g,b,a){
-var rval=this.fromRGB(r,g,b,a);
-this[name]=function(){
-return rval;
-};
-return rval;
-};
-for(var k in _542){
-var name=k+"Color";
-var _545=m.concat([_543,this.Color,name],_542[k]);
-this.Color[name]=m.bind.apply(null,_545);
-}
-var _546=function(){
-for(var i=0;i<arguments.length;i++){
-if(!(arguments[i] instanceof Color)){
-return false;
-}
-}
-return true;
-};
-var _547=function(a,b){
-return a.compareRGB(b);
-};
-m.nameFunctions(this);
-m.registerComparator(this.Color.NAME,_546,_547);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-}});
-MochiKit.Color.EXPORT=["Color"];
-MochiKit.Color.EXPORT_OK=["clampColorComponent","rgbToHSL","hslToRGB","rgbToHSV","hsvToRGB","toColorPart"];
-MochiKit.Color.__new__();
-MochiKit.Base._exportSymbols(this,MochiKit.Color);
-MochiKit.Color.Color._namedColors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.Signal");
-dojo.require("MochiKit.Base");
-dojo.require("MochiKit.DOM");
-}
-if(typeof (JSAN)!="undefined"){
-JSAN.use("MochiKit.Base",[]);
-JSAN.use("MochiKit.DOM",[]);
-}
-try{
-if(typeof (MochiKit.Base)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "MochiKit.Signal depends on MochiKit.Base!";
-}
-try{
-if(typeof (MochiKit.DOM)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "MochiKit.Signal depends on MochiKit.DOM!";
-}
-if(typeof (MochiKit.Signal)=="undefined"){
-MochiKit.Signal={};
-}
-MochiKit.Signal.NAME="MochiKit.Signal";
-MochiKit.Signal.VERSION="1.3.1";
-MochiKit.Signal._observers=[];
-MochiKit.Signal.Event=function(src,e){
-this._event=e||window.event;
-this._src=src;
-};
-MochiKit.Base.update(MochiKit.Signal.Event.prototype,{__repr__:function(){
-var repr=MochiKit.Base.repr;
-var str="{event(): "+repr(this.event())+", src(): "+repr(this.src())+", type(): "+repr(this.type())+", target(): "+repr(this.target())+", modifier(): "+"{alt: "+repr(this.modifier().alt)+", ctrl: "+repr(this.modifier().ctrl)+", meta: "+repr(this.modifier().meta)+", shift: "+repr(this.modifier().shift)+", any: "+repr(this.modifier().any)+"}";
-if(this.type()&&this.type().indexOf("key")===0){
-str+=", key(): {code: "+repr(this.key().code)+", string: "+repr(this.key().string)+"}";
-}
-if(this.type()&&(this.type().indexOf("mouse")===0||this.type().indexOf("click")!=-1||this.type()=="contextmenu")){
-str+=", mouse(): {page: "+repr(this.mouse().page)+", client: "+repr(this.mouse().client);
-if(this.type()!="mousemove"){
-str+=", button: {left: "+repr(this.mouse().button.left)+", middle: "+repr(this.mouse().button.middle)+", right: "+repr(this.mouse().button.right)+"}}";
-}else{
-str+="}";
-}
-}
-if(this.type()=="mouseover"||this.type()=="mouseout"){
-str+=", relatedTarget(): "+repr(this.relatedTarget());
-}
-str+="}";
-return str;
-},toString:function(){
-return this.__repr__();
-},src:function(){
-return this._src;
-},event:function(){
-return this._event;
-},type:function(){
-return this._event.type||undefined;
-},target:function(){
-return this._event.target||this._event.srcElement;
-},relatedTarget:function(){
-if(this.type()=="mouseover"){
-return (this._event.relatedTarget||this._event.fromElement);
-}else{
-if(this.type()=="mouseout"){
-return (this._event.relatedTarget||this._event.toElement);
-}
-}
-return undefined;
-},modifier:function(){
-var m={};
-m.alt=this._event.altKey;
-m.ctrl=this._event.ctrlKey;
-m.meta=this._event.metaKey||false;
-m.shift=this._event.shiftKey;
-m.any=m.alt||m.ctrl||m.shift||m.meta;
-return m;
-},key:function(){
-var k={};
-if(this.type()&&this.type().indexOf("key")===0){
-if(this.type()=="keydown"||this.type()=="keyup"){
-k.code=this._event.keyCode;
-k.string=(MochiKit.Signal._specialKeys[k.code]||"KEY_UNKNOWN");
-return k;
-}else{
-if(this.type()=="keypress"){
-k.code=0;
-k.string="";
-if(typeof (this._event.charCode)!="undefined"&&this._event.charCode!==0&&!MochiKit.Signal._specialMacKeys[this._event.charCode]){
-k.code=this._event.charCode;
-k.string=String.fromCharCode(k.code);
-}else{
-if(this._event.keyCode&&typeof (this._event.charCode)=="undefined"){
-k.code=this._event.keyCode;
-k.string=String.fromCharCode(k.code);
-}
-}
-return k;
-}
-}
-}
-return undefined;
-},mouse:function(){
-var m={};
-var e=this._event;
-if(this.type()&&(this.type().indexOf("mouse")===0||this.type().indexOf("click")!=-1||this.type()=="contextmenu")){
-m.client=new MochiKit.DOM.Coordinates(0,0);
-if(e.clientX||e.clientY){
-m.client.x=(!e.clientX||e.clientX<0)?0:e.clientX;
-m.client.y=(!e.clientY||e.clientY<0)?0:e.clientY;
-}
-m.page=new MochiKit.DOM.Coordinates(0,0);
-if(e.pageX||e.pageY){
-m.page.x=(!e.pageX||e.pageX<0)?0:e.pageX;
-m.page.y=(!e.pageY||e.pageY<0)?0:e.pageY;
-}else{
-var de=MochiKit.DOM._document.documentElement;
-var b=MochiKit.DOM._document.body;
-m.page.x=e.clientX+(de.scrollLeft||b.scrollLeft)-(de.clientLeft||b.clientLeft);
-m.page.y=e.clientY+(de.scrollTop||b.scrollTop)-(de.clientTop||b.clientTop);
-}
-if(this.type()!="mousemove"){
-m.button={};
-m.button.left=false;
-m.button.right=false;
-m.button.middle=false;
-if(e.which){
-m.button.left=(e.which==1);
-m.button.middle=(e.which==2);
-m.button.right=(e.which==3);
-}else{
-m.button.left=!!(e.button&1);
-m.button.right=!!(e.button&2);
-m.button.middle=!!(e.button&4);
-}
-}
-return m;
-}
-return undefined;
-},stop:function(){
-this.stopPropagation();
-this.preventDefault();
-},stopPropagation:function(){
-if(this._event.stopPropagation){
-this._event.stopPropagation();
-}else{
-this._event.cancelBubble=true;
-}
-},preventDefault:function(){
-if(this._event.preventDefault){
-this._event.preventDefault();
-}else{
-this._event.returnValue=false;
-}
-}});
-MochiKit.Signal._specialMacKeys={3:"KEY_ENTER",63289:"KEY_NUM_PAD_CLEAR",63276:"KEY_PAGE_UP",63277:"KEY_PAGE_DOWN",63275:"KEY_END",63273:"KEY_HOME",63234:"KEY_ARROW_LEFT",63232:"KEY_ARROW_UP",63235:"KEY_ARROW_RIGHT",63233:"KEY_ARROW_DOWN",63302:"KEY_INSERT",63272:"KEY_DELETE"};
-for(i=63236;i<=63242;i++){
-MochiKit.Signal._specialMacKeys[i]="KEY_F"+(i-63236+1);
-}
-MochiKit.Signal._specialKeys={8:"KEY_BACKSPACE",9:"KEY_TAB",12:"KEY_NUM_PAD_CLEAR",13:"KEY_ENTER",16:"KEY_SHIFT",17:"KEY_CTRL",18:"KEY_ALT",19:"KEY_PAUSE",20:"KEY_CAPS_LOCK",27:"KEY_ESCAPE",32:"KEY_SPACEBAR",33:"KEY_PAGE_UP",34:"KEY_PAGE_DOWN",35:"KEY_END",36:"KEY_HOME",37:"KEY_ARROW_LEFT",38:"KEY_ARROW_UP",39:"KEY_ARROW_RIGHT",40:"KEY_ARROW_DOWN",44:"KEY_PRINT_SCREEN",45:"KEY_INSERT",46:"KEY_DELETE",59:"KEY_SEMICOLON",91:"KEY_WINDOWS_LEFT",92:"KEY_WINDOWS_RIGHT",93:"KEY_SELECT",106:"KEY_NUM_PAD_ASTERISK",107:"KEY_NUM_PAD_PLUS_SIGN",109:"KEY_NUM_PAD_HYPHEN-MINUS",110:"KEY_NUM_PAD_FULL_STOP",111:"KEY_NUM_PAD_SOLIDUS",144:"KEY_NUM_LOCK",145:"KEY_SCROLL_LOCK",186:"KEY_SEMICOLON",187:"KEY_EQUALS_SIGN",188:"KEY_COMMA",189:"KEY_HYPHEN-MINUS",190:"KEY_FULL_STOP",191:"KEY_SOLIDUS",192:"KEY_GRAVE_ACCENT",219:"KEY_LEFT_SQUARE_BRACKET",220:"KEY_REVERSE_SOLIDUS",221:"KEY_RIGHT_SQUARE_BRACKET",222:"KEY_APOSTROPHE"};
-for(var i=48;i<=57;i++){
-MochiKit.Signal._specialKeys[i]="KEY_"+(i-48);
-}
-for(i=65;i<=90;i++){
-MochiKit.Signal._specialKeys[i]="KEY_"+String.fromCharCode(i);
-}
-for(i=96;i<=105;i++){
-MochiKit.Signal._specialKeys[i]="KEY_NUM_PAD_"+(i-96);
-}
-for(i=112;i<=123;i++){
-MochiKit.Signal._specialKeys[i]="KEY_F"+(i-112+1);
-}
-MochiKit.Base.update(MochiKit.Signal,{__repr__:function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-},toString:function(){
-return this.__repr__();
-},_unloadCache:function(){
-var self=MochiKit.Signal;
-var _548=self._observers;
-for(var i=0;i<_548.length;i++){
-self._disconnect(_548[i]);
-}
-delete self._observers;
-try{
-window.onload=undefined;
-}
-catch(e){
-}
-try{
-window.onunload=undefined;
-}
-catch(e){
-}
-},_listener:function(src,func,obj,_549){
-var E=MochiKit.Signal.Event;
-if(!_549){
-return MochiKit.Base.bind(func,obj);
-}
-obj=obj||src;
-if(typeof (func)=="string"){
-return function(_551){
-obj[func].apply(obj,[new E(src,_551)]);
-};
-}else{
-return function(_552){
-func.apply(obj,[new E(src,_552)]);
-};
-}
-},connect:function(src,sig,_554,_555){
-src=MochiKit.DOM.getElement(src);
-var self=MochiKit.Signal;
-if(typeof (sig)!="string"){
-throw new Error("'sig' must be a string");
-}
-var obj=null;
-var func=null;
-if(typeof (_555)!="undefined"){
-obj=_554;
-func=_555;
-if(typeof (_555)=="string"){
-if(typeof (_554[_555])!="function"){
-throw new Error("'funcOrStr' must be a function on 'objOrFunc'");
-}
-}else{
-if(typeof (_555)!="function"){
-throw new Error("'funcOrStr' must be a function or string");
-}
-}
-}else{
-if(typeof (_554)!="function"){
-throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given");
-}else{
-func=_554;
-}
-}
-if(typeof (obj)=="undefined"||obj===null){
-obj=src;
-}
-var _556=!!(src.addEventListener||src.attachEvent);
-var _557=self._listener(src,func,obj,_556);
-if(src.addEventListener){
-src.addEventListener(sig.substr(2),_557,false);
-}else{
-if(src.attachEvent){
-src.attachEvent(sig,_557);
-}
-}
-var _558=[src,sig,_557,_556,_554,_555];
-self._observers.push(_558);
-return _558;
-},_disconnect:function(_559){
-if(!_559[3]){
-return;
-}
-var src=_559[0];
-var sig=_559[1];
-var _560=_559[2];
-if(src.removeEventListener){
-src.removeEventListener(sig.substr(2),_560,false);
-}else{
-if(src.detachEvent){
-src.detachEvent(sig,_560);
-}else{
-throw new Error("'src' must be a DOM element");
-}
-}
-},disconnect:function(_561){
-var self=MochiKit.Signal;
-var _562=self._observers;
-var m=MochiKit.Base;
-if(arguments.length>1){
-var src=MochiKit.DOM.getElement(arguments[0]);
-var sig=arguments[1];
-var obj=arguments[2];
-var func=arguments[3];
-for(var i=_562.length-1;i>=0;i--){
-var o=_562[i];
-if(o[0]===src&&o[1]===sig&&o[4]===obj&&o[5]===func){
-self._disconnect(o);
-_562.splice(i,1);
-return true;
-}
-}
-}else{
-var idx=m.findIdentical(_562,_561);
-if(idx>=0){
-self._disconnect(_561);
-_562.splice(idx,1);
-return true;
-}
-}
-return false;
-},disconnectAll:function(src,sig){
-src=MochiKit.DOM.getElement(src);
-var m=MochiKit.Base;
-var _563=m.flattenArguments(m.extend(null,arguments,1));
-var self=MochiKit.Signal;
-var _564=self._disconnect;
-var _565=self._observers;
-if(_563.length===0){
-for(var i=_565.length-1;i>=0;i--){
-var _566=_565[i];
-if(_566[0]===src){
-_564(_566);
-_565.splice(i,1);
-}
-}
-}else{
-var sigs={};
-for(var i=0;i<_563.length;i++){
-sigs[_563[i]]=true;
-}
-for(var i=_565.length-1;i>=0;i--){
-var _566=_565[i];
-if(_566[0]===src&&_566[1] in sigs){
-_564(_566);
-_565.splice(i,1);
-}
-}
-}
-},signal:function(src,sig){
-var _568=MochiKit.Signal._observers;
-src=MochiKit.DOM.getElement(src);
-var args=MochiKit.Base.extend(null,arguments,2);
-var _569=[];
-for(var i=0;i<_568.length;i++){
-var _570=_568[i];
-if(_570[0]===src&&_570[1]===sig){
-try{
-_570[2].apply(src,args);
-}
-catch(e){
-_569.push(e);
-}
-}
-}
-if(_569.length==1){
-throw _569[0];
-}else{
-if(_569.length>1){
-var e=new Error("Multiple errors thrown in handling 'sig', see errors property");
-e.errors=_569;
-throw e;
-}
-}
-}});
-MochiKit.Signal.EXPORT_OK=[];
-MochiKit.Signal.EXPORT=["connect","disconnect","signal","disconnectAll"];
-MochiKit.Signal.__new__=function(win){
-var m=MochiKit.Base;
-this._document=document;
-this._window=win;
-try{
-this.connect(window,"onunload",this._unloadCache);
-}
-catch(e){
-}
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-m.nameFunctions(this);
-};
-MochiKit.Signal.__new__(this);
-if(!MochiKit.__compat__){
-connect=MochiKit.Signal.connect;
-disconnect=MochiKit.Signal.disconnect;
-disconnectAll=MochiKit.Signal.disconnectAll;
-signal=MochiKit.Signal.signal;
-}
-MochiKit.Base._exportSymbols(this,MochiKit.Signal);
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.Visual");
-dojo.require("MochiKit.Base");
-dojo.require("MochiKit.DOM");
-dojo.require("MochiKit.Color");
-}
-if(typeof (JSAN)!="undefined"){
-JSAN.use("MochiKit.Base",[]);
-JSAN.use("MochiKit.DOM",[]);
-JSAN.use("MochiKit.Color",[]);
-}
-try{
-if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.DOM)=="undefined"||typeof (MochiKit.Color)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "MochiKit.Visual depends on MochiKit.Base, MochiKit.DOM and MochiKit.Color!";
-}
-if(typeof (MochiKit.Visual)=="undefined"){
-MochiKit.Visual={};
-}
-MochiKit.Visual.NAME="MochiKit.Visual";
-MochiKit.Visual.VERSION="1.3.1";
-MochiKit.Visual.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-MochiKit.Visual.toString=function(){
-return this.__repr__();
-};
-MochiKit.Visual._RoundCorners=function(e,_571){
-e=MochiKit.DOM.getElement(e);
-this._setOptions(_571);
-if(this.options.__unstable__wrapElement){
-e=this._doWrap(e);
-}
-var _572=this.options.color;
-var C=MochiKit.Color.Color;
-if(this.options.color=="fromElement"){
-_572=C.fromBackground(e);
-}else{
-if(!(_572 instanceof C)){
-_572=C.fromString(_572);
-}
-}
-this.isTransparent=(_572.asRGB().a<=0);
-var _574=this.options.bgColor;
-if(this.options.bgColor=="fromParent"){
-_574=C.fromBackground(e.offsetParent);
-}else{
-if(!(_574 instanceof C)){
-_574=C.fromString(_574);
-}
-}
-this._roundCornersImpl(e,_572,_574);
-};
-MochiKit.Visual._RoundCorners.prototype={_doWrap:function(e){
-var _575=e.parentNode;
-var doc=MochiKit.DOM.currentDocument();
-if(typeof (doc.defaultView)=="undefined"||doc.defaultView===null){
-return e;
-}
-var _576=doc.defaultView.getComputedStyle(e,null);
-if(typeof (_576)=="undefined"||_576===null){
-return e;
-}
-var _577=MochiKit.DOM.DIV({"style":{display:"block",marginTop:_576.getPropertyValue("padding-top"),marginRight:_576.getPropertyValue("padding-right"),marginBottom:_576.getPropertyValue("padding-bottom"),marginLeft:_576.getPropertyValue("padding-left"),padding:"0px"}});
-_577.innerHTML=e.innerHTML;
-e.innerHTML="";
-e.appendChild(_577);
-return e;
-},_roundCornersImpl:function(e,_578,_579){
-if(this.options.border){
-this._renderBorder(e,_579);
-}
-if(this._isTopRounded()){
-this._roundTopCorners(e,_578,_579);
-}
-if(this._isBottomRounded()){
-this._roundBottomCorners(e,_578,_579);
-}
-},_renderBorder:function(el,_580){
-var _581="1px solid "+this._borderColor(_580);
-var _582="border-left: "+_581;
-var _583="border-right: "+_581;
-var _584="style='"+_582+";"+_583+"'";
-el.innerHTML="<div "+_584+">"+el.innerHTML+"</div>";
-},_roundTopCorners:function(el,_585,_586){
-var _587=this._createCorner(_586);
-for(var i=0;i<this.options.numSlices;i++){
-_587.appendChild(this._createCornerSlice(_585,_586,i,"top"));
-}
-el.style.paddingTop=0;
-el.insertBefore(_587,el.firstChild);
-},_roundBottomCorners:function(el,_588,_589){
-var _590=this._createCorner(_589);
-for(var i=(this.options.numSlices-1);i>=0;i--){
-_590.appendChild(this._createCornerSlice(_588,_589,i,"bottom"));
-}
-el.style.paddingBottom=0;
-el.appendChild(_590);
-},_createCorner:function(_591){
-var dom=MochiKit.DOM;
-return dom.DIV({style:{backgroundColor:_591.toString()}});
-},_createCornerSlice:function(_592,_593,n,_594){
-var _595=MochiKit.DOM.SPAN();
-var _596=_595.style;
-_596.backgroundColor=_592.toString();
-_596.display="block";
-_596.height="1px";
-_596.overflow="hidden";
-_596.fontSize="1px";
-var _597=this._borderColor(_592,_593);
-if(this.options.border&&n===0){
-_596.borderTopStyle="solid";
-_596.borderTopWidth="1px";
-_596.borderLeftWidth="0px";
-_596.borderRightWidth="0px";
-_596.borderBottomWidth="0px";
-_596.height="0px";
-_596.borderColor=_597.toString();
-}else{
-if(_597){
-_596.borderColor=_597.toString();
-_596.borderStyle="solid";
-_596.borderWidth="0px 1px";
-}
-}
-if(!this.options.compact&&(n==(this.options.numSlices-1))){
-_596.height="2px";
-}
-this._setMargin(_595,n,_594);
-this._setBorder(_595,n,_594);
-return _595;
-},_setOptions:function(_598){
-this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false,__unstable__wrapElement:false};
-MochiKit.Base.update(this.options,_598);
-this.options.numSlices=(this.options.compact?2:4);
-},_whichSideTop:function(){
-var _599=this.options.corners;
-if(this._hasString(_599,"all","top")){
-return "";
-}
-var _600=(_599.indexOf("tl")!=-1);
-var _601=(_599.indexOf("tr")!=-1);
-if(_600&&_601){
-return "";
-}
-if(_600){
-return "left";
-}
-if(_601){
-return "right";
-}
-return "";
-},_whichSideBottom:function(){
-var _602=this.options.corners;
-if(this._hasString(_602,"all","bottom")){
-return "";
-}
-var _603=(_602.indexOf("bl")!=-1);
-var _604=(_602.indexOf("br")!=-1);
-if(_603&&_604){
-return "";
-}
-if(_603){
-return "left";
-}
-if(_604){
-return "right";
-}
-return "";
-},_borderColor:function(_605,_606){
-if(_605=="transparent"){
-return _606;
-}else{
-if(this.options.border){
-return this.options.border;
-}else{
-if(this.options.blend){
-return _606.blendedColor(_605);
-}
-}
-}
-return "";
-},_setMargin:function(el,n,_607){
-var _608=this._marginSize(n)+"px";
-var _609=(_607=="top"?this._whichSideTop():this._whichSideBottom());
-var _610=el.style;
-if(_609=="left"){
-_610.marginLeft=_608;
-_610.marginRight="0px";
-}else{
-if(_609=="right"){
-_610.marginRight=_608;
-_610.marginLeft="0px";
-}else{
-_610.marginLeft=_608;
-_610.marginRight=_608;
-}
-}
-},_setBorder:function(el,n,_611){
-var _612=this._borderSize(n)+"px";
-var _613=(_611=="top"?this._whichSideTop():this._whichSideBottom());
-var _614=el.style;
-if(_613=="left"){
-_614.borderLeftWidth=_612;
-_614.borderRightWidth="0px";
-}else{
-if(_613=="right"){
-_614.borderRightWidth=_612;
-_614.borderLeftWidth="0px";
-}else{
-_614.borderLeftWidth=_612;
-_614.borderRightWidth=_612;
-}
-}
-},_marginSize:function(n){
-if(this.isTransparent){
-return 0;
-}
-var o=this.options;
-if(o.compact&&o.blend){
-var _615=[1,0];
-return _615[n];
-}else{
-if(o.compact){
-var _616=[2,1];
-return _616[n];
-}else{
-if(o.blend){
-var _617=[3,2,1,0];
-return _617[n];
-}else{
-var _618=[5,3,2,1];
-return _618[n];
-}
-}
-}
-},_borderSize:function(n){
-var o=this.options;
-var _619;
-if(o.compact&&(o.blend||this.isTransparent)){
-return 1;
-}else{
-if(o.compact){
-_619=[1,0];
-}else{
-if(o.blend){
-_619=[2,1,1,1];
-}else{
-if(o.border){
-_619=[0,2,0,0];
-}else{
-if(this.isTransparent){
-_619=[5,3,2,1];
-}else{
-return 0;
-}
-}
-}
-}
-}
-return _619[n];
-},_hasString:function(str){
-for(var i=1;i<arguments.length;i++){
-if(str.indexOf(arguments[i])!=-1){
-return true;
-}
-}
-return false;
-},_isTopRounded:function(){
-return this._hasString(this.options.corners,"all","top","tl","tr");
-},_isBottomRounded:function(){
-return this._hasString(this.options.corners,"all","bottom","bl","br");
-},_hasSingleTextChild:function(el){
-return (el.childNodes.length==1&&el.childNodes[0].nodeType==3);
-}};
-MochiKit.Visual.roundElement=function(e,_620){
-new MochiKit.Visual._RoundCorners(e,_620);
-};
-MochiKit.Visual.roundClass=function(_621,_622,_623){
-var _624=MochiKit.DOM.getElementsByTagAndClassName(_621,_622);
-for(var i=0;i<_624.length;i++){
-MochiKit.Visual.roundElement(_624[i],_623);
-}
-};
-MochiKit.Visual.Color=MochiKit.Color.Color;
-MochiKit.Visual.getElementsComputedStyle=MochiKit.DOM.computedStyle;
-MochiKit.Visual.__new__=function(){
-var m=MochiKit.Base;
-m.nameFunctions(this);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-};
-MochiKit.Visual.EXPORT=["roundElement","roundClass"];
-MochiKit.Visual.EXPORT_OK=[];
-MochiKit.Visual.__new__();
-MochiKit.Base._exportSymbols(this,MochiKit.Visual);
-if(typeof (MochiKit)=="undefined"){
-MochiKit={};
-}
-if(typeof (MochiKit.MochiKit)=="undefined"){
-MochiKit.MochiKit={};
-}
-MochiKit.MochiKit.NAME="MochiKit.MochiKit";
-MochiKit.MochiKit.VERSION="1.3.1";
-MochiKit.MochiKit.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-MochiKit.MochiKit.toString=function(){
-return this.__repr__();
-};
-MochiKit.MochiKit.SUBMODULES=["Base","Iter","Logging","DateTime","Format","Async","DOM","LoggingPane","Color","Signal","Visual"];
-if(typeof (JSAN)!="undefined"||typeof (dojo)!="undefined"){
-if(typeof (dojo)!="undefined"){
-dojo.provide("MochiKit.MochiKit");
-dojo.require("MochiKit.*");
-}
-if(typeof (JSAN)!="undefined"){
-JSAN.use("MochiKit.Base",[]);
-JSAN.use("MochiKit.Iter",[]);
-JSAN.use("MochiKit.Logging",[]);
-JSAN.use("MochiKit.DateTime",[]);
-JSAN.use("MochiKit.Format",[]);
-JSAN.use("MochiKit.Async",[]);
-JSAN.use("MochiKit.DOM",[]);
-JSAN.use("MochiKit.LoggingPane",[]);
-JSAN.use("MochiKit.Color",[]);
-JSAN.use("MochiKit.Signal",[]);
-JSAN.use("MochiKit.Visual",[]);
-}
-(function(){
-var _625=MochiKit.Base.extend;
-var self=MochiKit.MochiKit;
-var _626=self.SUBMODULES;
-var _627=[];
-var _628=[];
-var _629={};
-var i,k,m,all;
-for(i=0;i<_626.length;i++){
-m=MochiKit[_626[i]];
-_625(_627,m.EXPORT);
-_625(_628,m.EXPORT_OK);
-for(k in m.EXPORT_TAGS){
-_629[k]=_625(_629[k],m.EXPORT_TAGS[k]);
-}
-all=m.EXPORT_TAGS[":all"];
-if(!all){
-all=_625(null,m.EXPORT,m.EXPORT_OK);
-}
-var j;
-for(j=0;j<all.length;j++){
-k=all[j];
-self[k]=m[k];
-}
-}
-self.EXPORT=_627;
-self.EXPORT_OK=_628;
-self.EXPORT_TAGS=_629;
-}());
-}else{
-if(typeof (MochiKit.__compat__)=="undefined"){
-MochiKit.__compat__=true;
-}
-(function(){
-var _630=document.getElementsByTagName("script");
-var _631="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-var base=null;
-var _632=null;
-var _633={};
-var i;
-for(i=0;i<_630.length;i++){
-var src=_630[i].getAttribute("src");
-if(!src){
-continue;
-}
-_633[src]=true;
-if(src.match(/MochiKit.js$/)){
-base=src.substring(0,src.lastIndexOf("MochiKit.js"));
-_632=_630[i];
-}
-}
-if(base===null){
-return;
-}
-var _634=MochiKit.MochiKit.SUBMODULES;
-for(var i=0;i<_634.length;i++){
-if(MochiKit[_634[i]]){
-continue;
-}
-var uri=base+_634[i]+".js";
-if(uri in _633){
-continue;
-}
-if(document.documentElement&&document.documentElement.namespaceURI==_631){
-var s=document.createElementNS(_631,"script");
-s.setAttribute("id","MochiKit_"+base+_634[i]);
-s.setAttribute("src",uri);
-s.setAttribute("type","application/x-javascript");
-_632.parentNode.appendChild(s);
-}else{
-document.write("<script src=\""+uri+"\" type=\"text/javascript\"></script>");
-}
-}
-})();
-}
-
-
diff --git a/lib/paste/evalexception/mochikit/__package__.js b/lib/paste/evalexception/mochikit/__package__.js
@@ -1,2 +0,0 @@
-dojo.hostenv.conditionalLoadModule({"common": ["MochiKit.MochiKit"]});
-dojo.hostenv.moduleLoaded("MochiKit.*");
diff --git a/lib/paste/exceptions/__init__.py b/lib/paste/exceptions/__init__.py
@@ -1,6 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Package for catching exceptions and displaying annotated exception
-reports
-"""
diff --git a/lib/paste/exceptions/collector.py b/lib/paste/exceptions/collector.py
@@ -1,526 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-## Originally zExceptions.ExceptionFormatter from Zope;
-## Modified by Ian Bicking, Imaginary Landscape, 2005
-"""
-An exception collector that finds traceback information plus
-supplements
-"""
-
-import sys
-import traceback
-import time
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-import linecache
-from paste.exceptions import serial_number_generator
-import warnings
-
-DEBUG_EXCEPTION_FORMATTER = True
-DEBUG_IDENT_PREFIX = 'E-'
-FALLBACK_ENCODING = 'UTF-8'
-
-__all__ = ['collect_exception', 'ExceptionCollector']
-
-class ExceptionCollector(object):
-
- """
- Produces a data structure that can be used by formatters to
- display exception reports.
-
- Magic variables:
-
- If you define one of these variables in your local scope, you can
- add information to tracebacks that happen in that context. This
- allows applications to add all sorts of extra information about
- the context of the error, including URLs, environmental variables,
- users, hostnames, etc. These are the variables we look for:
-
- ``__traceback_supplement__``:
- You can define this locally or globally (unlike all the other
- variables, which must be defined locally).
-
- ``__traceback_supplement__`` is a tuple of ``(factory, arg1,
- arg2...)``. When there is an exception, ``factory(arg1, arg2,
- ...)`` is called, and the resulting object is inspected for
- supplemental information.
-
- ``__traceback_info__``:
- This information is added to the traceback, usually fairly
- literally.
-
- ``__traceback_hide__``:
- If set and true, this indicates that the frame should be
- hidden from abbreviated tracebacks. This way you can hide
- some of the complexity of the larger framework and let the
- user focus on their own errors.
-
- By setting it to ``'before'``, all frames before this one will
- be thrown away. By setting it to ``'after'`` then all frames
- after this will be thrown away until ``'reset'`` is found. In
- each case the frame where it is set is included, unless you
- append ``'_and_this'`` to the value (e.g.,
- ``'before_and_this'``).
-
- Note that formatters will ignore this entirely if the frame
- that contains the error wouldn't normally be shown according
- to these rules.
-
- ``__traceback_reporter__``:
- This should be a reporter object (see the reporter module),
- or a list/tuple of reporter objects. All reporters found this
- way will be given the exception, innermost first.
-
- ``__traceback_decorator__``:
- This object (defined in a local or global scope) will get the
- result of this function (the CollectedException defined
- below). It may modify this object in place, or return an
- entirely new object. This gives the object the ability to
- manipulate the traceback arbitrarily.
-
- 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:
-
- ``frames``:
- A list of frames
- ``exception_formatted``:
- The formatted exception, generally a full traceback
- ``exception_type``:
- The type of the exception, like ``ValueError``
- ``exception_value``:
- The string value of the exception, like ``'x not in list'``
- ``identification_code``:
- A hash of the exception data meant to identify the general
- exception, so that it shares this code with other exceptions
- that derive from the same problem. The code is a hash of
- all the module names and function names in the traceback,
- plus exception_type. This should be shown to users so they
- 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.
-
- ``modname``:
- the name of the module
- ``filename``:
- the filename of the module
- ``lineno``:
- the line of the error
- ``revision``:
- the contents of __version__ or __revision__
- ``name``:
- the function name
- ``supplement``:
- an object created from ``__traceback_supplement__``
- ``supplement_exception``:
- a simple traceback of any exception ``__traceback_supplement__``
- created
- ``traceback_info``:
- the str() of any ``__traceback_info__`` variable found in the local
- scope (@@: should it str()-ify it or not?)
- ``traceback_hide``:
- 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
- optional.
-
- ``object``:
- the name of the object being visited
- ``source_url``:
- the original URL requested
- ``line``:
- the line of source being executed (for interpreters, like ZPT)
- ``column``:
- the column of source being executed
- ``expression``:
- the expression being evaluated (also for interpreters)
- ``warnings``:
- a list of (string) warnings to be displayed
- ``getInfo``:
- a function/method that takes no arguments, and returns a string
- describing any extra information
- ``extraData``:
- a function/method that takes no arguments, and returns a
- dictionary. The contents of this dictionary will not be
- displayed in the context of the traceback, but globally for
- the exception. Results will be grouped by the keys in the
- dictionaries (which also serve as titles). The keys can also
- be tuples of (importance, title); in this case the importance
- should be ``important`` (shows up at top), ``normal`` (shows
- up somewhere; unspecified), ``supplemental`` (shows up at
- bottom), or ``extra`` (shows up hidden or not at all).
-
- These are used to create an object with attributes of the same
- names (``getInfo`` becomes a string attribute, not a method).
- ``__traceback_supplement__`` implementations should be careful to
- produce values that are relatively static and unlikely to cause
- further errors in the reporting system -- any complex
- introspection should go in ``getInfo()`` and should ultimately
- return a string.
-
- Note that all attributes are optional, and under certain
- circumstances may be None or may not exist at all -- the collector
- can only do a best effort, but must avoid creating any exceptions
- itself.
-
- Formatters may want to use ``__traceback_hide__`` as a hint to
- 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
- that gives a list of local variables that should also be
- collected? Also, attributes that would be explicitly meant for
- the entire request, not just a single frame. Right now some of
- the fixed set of attributes (e.g., source_url) are meant for this
- use, but there's no explicit way for the supplement to indicate
- new values, e.g., logged-in user, HTTP referrer, environment, etc.
- Also, the attributes that do exist are Zope/Web oriented.
-
- More information on frames? cgitb, for instance, produces
- extensive information on local variables. There exists the
- possibility that getting this information may cause side effects,
- which can make debugging more difficult; but it also provides
- fodder for post-mortem debugging. However, the collector is not
- meant to be configurable, but to capture everything it can and let
- the formatters be configurable. Maybe this would have to be a
- configuration value, or maybe it could be indicated by another
- magical variable (which would probably mean 'show all local
- variables below this frame')
- """
-
- show_revisions = 0
-
- def __init__(self, limit=None):
- self.limit = limit
-
- def getLimit(self):
- limit = self.limit
- if limit is None:
- limit = getattr(sys, 'tracebacklimit', None)
- return limit
-
- def getRevision(self, globals):
- if not self.show_revisions:
- return None
- revision = globals.get('__revision__', None)
- if revision is None:
- # Incorrect but commonly used spelling
- revision = globals.get('__version__', None)
-
- if revision is not None:
- try:
- revision = str(revision).strip()
- except:
- revision = '???'
- return revision
-
- def collectSupplement(self, supplement, tb):
- result = {}
-
- for name in ('object', 'source_url', 'line', 'column',
- 'expression', 'warnings'):
- result[name] = getattr(supplement, name, None)
-
- func = getattr(supplement, 'getInfo', None)
- if func:
- result['info'] = func()
- else:
- result['info'] = None
- func = getattr(supplement, 'extraData', None)
- if func:
- result['extra'] = func()
- else:
- result['extra'] = None
- return SupplementaryData(**result)
-
- def collectLine(self, tb, extra_data):
- f = tb.tb_frame
- lineno = tb.tb_lineno
- co = f.f_code
- filename = co.co_filename
- name = co.co_name
- globals = f.f_globals
- locals = f.f_locals
- if not hasattr(locals, 'has_key'):
- # Something weird about this frame; it's not a real dict
- warnings.warn(
- "Frame %s has an invalid locals(): %r" % (
- globals.get('__name__', 'unknown'), locals))
- locals = {}
- data = {}
- data['modname'] = globals.get('__name__', None)
- data['filename'] = filename
- data['lineno'] = lineno
- data['revision'] = self.getRevision(globals)
- data['name'] = name
- data['tbid'] = id(tb)
-
- # Output a traceback supplement, if any.
- if locals.has_key('__traceback_supplement__'):
- # Use the supplement defined in the function.
- tbs = locals['__traceback_supplement__']
- elif globals.has_key('__traceback_supplement__'):
- # Use the supplement defined in the module.
- # This is used by Scripts (Python).
- tbs = globals['__traceback_supplement__']
- else:
- tbs = None
- if tbs is not None:
- factory = tbs[0]
- args = tbs[1:]
- try:
- supp = factory(*args)
- data['supplement'] = self.collectSupplement(supp, tb)
- if data['supplement'].extra:
- for key, value in data['supplement'].extra.items():
- extra_data.setdefault(key, []).append(value)
- except:
- if DEBUG_EXCEPTION_FORMATTER:
- out = StringIO()
- traceback.print_exc(file=out)
- text = out.getvalue()
- data['supplement_exception'] = text
- # else just swallow the exception.
-
- try:
- tbi = locals.get('__traceback_info__', None)
- if tbi is not None:
- data['traceback_info'] = str(tbi)
- except:
- pass
-
- marker = []
- for name in ('__traceback_hide__', '__traceback_log__',
- '__traceback_decorator__'):
- try:
- tbh = locals.get(name, globals.get(name, marker))
- if tbh is not marker:
- data[name[2:-2]] = tbh
- except:
- pass
-
- return data
-
- def collectExceptionOnly(self, etype, value):
- return traceback.format_exception_only(etype, value)
-
- def collectException(self, etype, value, tb, limit=None):
- # The next line provides a way to detect recursion.
- __exception_formatter__ = 1
- frames = []
- ident_data = []
- traceback_decorators = []
- if limit is None:
- limit = self.getLimit()
- n = 0
- extra_data = {}
- while tb is not None and (limit is None or n < limit):
- if tb.tb_frame.f_locals.get('__exception_formatter__'):
- # Stop recursion. @@: should make a fake ExceptionFrame
- frames.append('(Recursive formatException() stopped)\n')
- break
- data = self.collectLine(tb, extra_data)
- frame = ExceptionFrame(**data)
- frames.append(frame)
- if frame.traceback_decorator is not None:
- traceback_decorators.append(frame.traceback_decorator)
- ident_data.append(frame.modname or '?')
- ident_data.append(frame.name or '?')
- tb = tb.tb_next
- n = n + 1
- ident_data.append(str(etype))
- ident = serial_number_generator.hash_identifier(
- ' '.join(ident_data), length=5, upper=True,
- prefix=DEBUG_IDENT_PREFIX)
-
- result = CollectedException(
- frames=frames,
- exception_formatted=self.collectExceptionOnly(etype, value),
- exception_type=etype,
- exception_value=self.safeStr(value),
- identification_code=ident,
- date=time.localtime(),
- extra_data=extra_data)
- if etype is ImportError:
- extra_data[('important', 'sys.path')] = [sys.path]
- for decorator in traceback_decorators:
- try:
- new_result = decorator(result)
- if new_result is not None:
- result = new_result
- except:
- pass
- return result
-
- def safeStr(self, obj):
- try:
- return str(obj)
- except UnicodeEncodeError:
- try:
- return unicode(obj).encode(FALLBACK_ENCODING, 'replace')
- except UnicodeEncodeError:
- # This is when something is really messed up, but this can
- # happen when the __str__ of an object has to handle unicode
- return repr(obj)
-
-limit = 200
-
-class Bunch(object):
-
- """
- A generic container
- """
-
- def __init__(self, **attrs):
- for name, value in attrs.items():
- setattr(self, name, value)
-
- def __repr__(self):
- name = '<%s ' % self.__class__.__name__
- name += ' '.join(['%s=%r' % (name, str(value)[:30])
- for name, value in self.__dict__.items()
- if not name.startswith('_')])
- return name + '>'
-
-class CollectedException(Bunch):
- """
- This is the result of collection the exception; it contains copies
- of data of interest.
- """
- # A list of frames (ExceptionFrame instances), innermost last:
- frames = []
- # The result of traceback.format_exception_only; this looks
- # like a normal traceback you'd see in the interactive interpreter
- exception_formatted = None
- # The *string* representation of the type of the exception
- # (@@: should we give the # actual class? -- we can't keep the
- # actual exception around, but the class should be safe)
- # Something like 'ValueError'
- exception_type = None
- # The string representation of the exception, from ``str(e)``.
- exception_value = None
- # An identifier which should more-or-less classify this particular
- # exception, including where in the code it happened.
- identification_code = None
- # The date, as time.localtime() returns:
- date = None
- # A dictionary of supplemental data:
- extra_data = {}
-
-class SupplementaryData(Bunch):
- """
- The result of __traceback_supplement__. We don't keep the
- supplement object around, for fear of GC problems and whatnot.
- (@@: Maybe I'm being too superstitious about copying only specific
- information over)
- """
-
- # These attributes are copied from the object, or left as None
- # if the object doesn't have these attributes:
- object = None
- source_url = None
- line = None
- column = None
- expression = None
- warnings = None
- # This is the *return value* of supplement.getInfo():
- info = None
-
-class ExceptionFrame(Bunch):
- """
- This represents one frame of the exception. Each frame is a
- context in the call stack, typically represented by a line
- number and module name in the traceback.
- """
-
- # The name of the module; can be None, especially when the code
- # isn't associated with a module.
- modname = None
- # The filename (@@: when no filename, is it None or '?'?)
- filename = None
- # Line number
- lineno = None
- # The value of __revision__ or __version__ -- but only if
- # show_revision = True (by defaut it is false). (@@: Why not
- # collect this?)
- revision = None
- # The name of the function with the error (@@: None or '?' when
- # unknown?)
- name = None
- # A SupplementaryData object, if __traceback_supplement__ was found
- # (and produced no errors)
- supplement = None
- # If accessing __traceback_supplement__ causes any error, the
- # plain-text traceback is stored here
- supplement_exception = None
- # The str() of any __traceback_info__ value found
- traceback_info = None
- # The value of __traceback_hide__
- traceback_hide = False
- # The value of __traceback_decorator__
- traceback_decorator = None
- # The id() of the traceback scope, can be used to reference the
- # scope for use elsewhere
- tbid = None
-
- def get_source_line(self, context=0):
- """
- Return the source of the current line of this frame. You
- probably want to .strip() it as well, as it is likely to have
- leading whitespace.
-
- If context is given, then that many lines on either side will
- also be returned. E.g., context=1 will give 3 lines.
- """
- if not self.filename or not self.lineno:
- return None
- lines = []
- 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)
-
-col = ExceptionCollector()
-
-def collect_exception(t, v, tb, limit=None):
- """
- Collection an exception from ``sys.exc_info()``.
-
- Use like::
-
- try:
- blah blah
- except:
- exc_data = collect_exception(*sys.exc_info())
- """
- return col.collectException(t, v, tb, limit=limit)
diff --git a/lib/paste/exceptions/errormiddleware.py b/lib/paste/exceptions/errormiddleware.py
@@ -1,460 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-Error handler middleware
-"""
-import sys
-import traceback
-import cgi
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-from paste.exceptions import formatter, collector, reporter
-from paste import wsgilib
-from paste import request
-
-__all__ = ['ErrorMiddleware', 'handle_exception']
-
-class _NoDefault(object):
- def __repr__(self):
- return '<NoDefault>'
-NoDefault = _NoDefault()
-
-class ErrorMiddleware(object):
-
- """
- Error handling middleware
-
- Usage::
-
- error_caching_wsgi_app = ErrorMiddleware(wsgi_app)
-
- Settings:
-
- ``debug``:
- If true, then tracebacks will be shown in the browser.
-
- ``error_email``:
- 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``
- (frequently a server error log, or stderr).
-
- ``from_address``, ``smtp_server``, ``error_subject_prefix``, ``smtp_username``, ``smtp_password``, ``smtp_use_tls``:
- variables to control the emailed exception reports
-
- ``error_message``:
- When debug mode is off, the error message to show to users.
-
- ``xmlhttp_key``:
- When this key (default ``_``) is in the request GET variables
- (not POST!), expect that this is an XMLHttpRequest, and the
- response should be more minimal; it should not be a complete
- 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
- where you don't want errors to be caught and transformed.
-
- ``paste.expected_exceptions``:
- When this middleware encounters an exception listed in this
- 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,
- 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.
-
- """
-
- def __init__(self, application, global_conf=None,
- debug=NoDefault,
- error_email=None,
- error_log=None,
- show_exceptions_in_wsgi_errors=NoDefault,
- from_address=None,
- smtp_server=None,
- smtp_username=None,
- smtp_password=None,
- smtp_use_tls=False,
- error_subject_prefix=None,
- error_message=None,
- xmlhttp_key=None):
- from paste.util import converters
- self.application = application
- # @@: global_conf should be handled elsewhere in a separate
- # function for the entry point
- if global_conf is None:
- global_conf = {}
- if debug is NoDefault:
- debug = converters.asbool(global_conf.get('debug'))
- if show_exceptions_in_wsgi_errors is NoDefault:
- show_exceptions_in_wsgi_errors = converters.asbool(global_conf.get('show_exceptions_in_wsgi_errors'))
- self.debug_mode = converters.asbool(debug)
- if error_email is None:
- error_email = (global_conf.get('error_email')
- or global_conf.get('admin_email')
- or global_conf.get('webmaster_email')
- or global_conf.get('sysadmin_email'))
- self.error_email = converters.aslist(error_email)
- self.error_log = error_log
- self.show_exceptions_in_wsgi_errors = show_exceptions_in_wsgi_errors
- if from_address is None:
- from_address = global_conf.get('error_from_address', 'errors@localhost')
- self.from_address = from_address
- if smtp_server is None:
- smtp_server = global_conf.get('smtp_server', 'localhost')
- self.smtp_server = smtp_server
- self.smtp_username = smtp_username or global_conf.get('smtp_username')
- self.smtp_password = smtp_password or global_conf.get('smtp_password')
- self.smtp_use_tls = smtp_use_tls or converters.asbool(global_conf.get('smtp_use_tls'))
- self.error_subject_prefix = error_subject_prefix or ''
- if error_message is None:
- error_message = global_conf.get('error_message')
- self.error_message = error_message
- 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.
- """
- # We want to be careful about not sending headers twice,
- # and the content type that the app has committed to (if there
- # is an exception in the iterator body of the response)
- if environ.get('paste.throw_errors'):
- return self.application(environ, start_response)
- environ['paste.throw_errors'] = True
-
- try:
- __traceback_supplement__ = Supplement, self, environ
- sr_checker = ResponseStartChecker(start_response)
- app_iter = self.application(environ, sr_checker)
- return self.make_catching_iter(app_iter, environ, sr_checker)
- except:
- exc_info = sys.exc_info()
- try:
- for expect in environ.get('paste.expected_exceptions', []):
- if isinstance(exc_info[1], expect):
- raise
- start_response('500 Internal Server Error',
- [('content-type', 'text/html')],
- exc_info)
- # @@: it would be nice to deal with bad content types here
- response = self.exception_handler(exc_info, environ)
- return [response]
- finally:
- # clean up locals...
- exc_info = None
-
- def make_catching_iter(self, app_iter, environ, sr_checker):
- if isinstance(app_iter, (list, tuple)):
- # These don't raise
- return app_iter
- return CatchingIter(app_iter, environ, sr_checker, self)
-
- def exception_handler(self, exc_info, environ):
- simple_html_error = False
- if self.xmlhttp_key:
- get_vars = wsgilib.parse_querystring(environ)
- if dict(get_vars).get(self.xmlhttp_key):
- simple_html_error = True
- return handle_exception(
- exc_info, environ['wsgi.errors'],
- html=True,
- debug_mode=self.debug_mode,
- error_email=self.error_email,
- error_log=self.error_log,
- show_exceptions_in_wsgi_errors=self.show_exceptions_in_wsgi_errors,
- error_email_from=self.from_address,
- smtp_server=self.smtp_server,
- smtp_username=self.smtp_username,
- smtp_password=self.smtp_password,
- smtp_use_tls=self.smtp_use_tls,
- error_subject_prefix=self.error_subject_prefix,
- error_message=self.error_message,
- simple_html_error=simple_html_error)
-
-class ResponseStartChecker(object):
- def __init__(self, start_response):
- self.start_response = start_response
- self.response_started = False
-
- def __call__(self, *args):
- self.response_started = True
- self.start_response(*args)
-
-class CatchingIter(object):
-
- """
- A wrapper around the application iterator that will catch
- exceptions raised by the a generator, or by the close method, and
- display or report as necessary.
- """
-
- def __init__(self, app_iter, environ, start_checker, error_middleware):
- self.app_iterable = app_iter
- self.app_iterator = iter(app_iter)
- self.environ = environ
- self.start_checker = start_checker
- self.error_middleware = error_middleware
- self.closed = False
-
- def __iter__(self):
- return self
-
- def next(self):
- __traceback_supplement__ = (
- Supplement, self.error_middleware, self.environ)
- if self.closed:
- raise StopIteration
- try:
- return self.app_iterator.next()
- except StopIteration:
- self.closed = True
- close_response = self._close()
- if close_response is not None:
- return close_response
- else:
- raise StopIteration
- except:
- self.closed = True
- close_response = self._close()
- exc_info = sys.exc_info()
- response = self.error_middleware.exception_handler(
- exc_info, self.environ)
- if close_response is not None:
- response += (
- '<hr noshade>Error in .close():<br>%s'
- % close_response)
-
- if not self.start_checker.response_started:
- self.start_checker('500 Internal Server Error',
- [('content-type', 'text/html')],
- exc_info)
-
- return response
-
- def close(self):
- # This should at least print something to stderr if the
- # close method fails at this point
- if not self.closed:
- self._close()
-
- def _close(self):
- """Close and return any error message"""
- if not hasattr(self.app_iterable, 'close'):
- return None
- try:
- self.app_iterable.close()
- return None
- except:
- close_response = self.error_middleware.exception_handler(
- sys.exc_info(), self.environ)
- return close_response
-
-
-class Supplement(object):
-
- """
- This is a supplement used to display standard WSGI information in
- the traceback.
- """
-
- def __init__(self, middleware, environ):
- self.middleware = middleware
- self.environ = environ
- self.source_url = request.construct_url(environ)
-
- def extraData(self):
- data = {}
- cgi_vars = data[('extra', 'CGI Variables')] = {}
- wsgi_vars = data[('extra', 'WSGI Variables')] = {}
- hide_vars = ['paste.config', 'wsgi.errors', 'wsgi.input',
- 'wsgi.multithread', 'wsgi.multiprocess',
- 'wsgi.run_once', 'wsgi.version',
- 'wsgi.url_scheme']
- for name, value in self.environ.items():
- if name.upper() == name:
- if value:
- cgi_vars[name] = value
- elif name not in hide_vars:
- wsgi_vars[name] = value
- if self.environ['wsgi.version'] != (1, 0):
- wsgi_vars['wsgi.version'] = self.environ['wsgi.version']
- proc_desc = tuple([int(bool(self.environ[key]))
- for key in ('wsgi.multiprocess',
- 'wsgi.multithread',
- 'wsgi.run_once')])
- wsgi_vars['wsgi process'] = self.process_combos[proc_desc]
- wsgi_vars['application'] = self.middleware.application
- if 'paste.config' in self.environ:
- data[('extra', 'Configuration')] = dict(self.environ['paste.config'])
- return data
-
- process_combos = {
- # multiprocess, multithread, run_once
- (0, 0, 0): 'Non-concurrent server',
- (0, 1, 0): 'Multithreaded',
- (1, 0, 0): 'Multiprocess',
- (1, 1, 0): 'Multi process AND threads (?)',
- (0, 0, 1): 'Non-concurrent CGI',
- (0, 1, 1): 'Multithread CGI (?)',
- (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,
- error_log=None,
- show_exceptions_in_wsgi_errors=False,
- error_email_from='errors@localhost',
- smtp_server='localhost',
- smtp_username=None,
- smtp_password=None,
- smtp_use_tls=False,
- error_subject_prefix='',
- error_message=None,
- simple_html_error=False,
- ):
- """
- For exception handling outside of a web context
-
- Use like::
-
- import sys
- from paste.exceptions.errormiddleware import handle_exception
- try:
- do stuff
- except:
- handle_exception(
- sys.exc_info(), sys.stderr, html=False, ...other config...)
-
- If you want to report, but not fully catch the exception, call
- ``raise`` after ``handle_exception``, which (when given no argument)
- will reraise the exception.
- """
- reported = False
- exc_data = collector.collect_exception(*exc_info)
- extra_data = ''
- if error_email:
- rep = reporter.EmailReporter(
- to_addresses=error_email,
- from_address=error_email_from,
- smtp_server=smtp_server,
- smtp_username=smtp_username,
- smtp_password=smtp_password,
- smtp_use_tls=smtp_use_tls,
- subject_prefix=error_subject_prefix)
- rep_err = send_report(rep, exc_data, html=html)
- if rep_err:
- extra_data += rep_err
- else:
- reported = True
- if error_log:
- rep = reporter.LogReporter(
- filename=error_log)
- rep_err = send_report(rep, exc_data, html=html)
- if rep_err:
- extra_data += rep_err
- else:
- reported = True
- if show_exceptions_in_wsgi_errors:
- rep = reporter.FileReporter(
- file=error_stream)
- rep_err = send_report(rep, exc_data, html=html)
- if rep_err:
- extra_data += rep_err
- else:
- reported = True
- else:
- error_stream.write('Error - %s: %s\n' % (
- exc_data.exception_type, exc_data.exception_value))
- if html:
- if debug_mode and simple_html_error:
- return_error = formatter.format_html(
- exc_data, include_hidden_frames=False,
- include_reusable=False, show_extra_data=False)
- reported = True
- elif debug_mode and not simple_html_error:
- error_html = formatter.format_html(
- exc_data,
- include_hidden_frames=True,
- include_reusable=False)
- head_html = formatter.error_css + formatter.hide_display_js
- return_error = error_template(
- head_html, error_html, extra_data)
- extra_data = ''
- reported = True
- else:
- msg = error_message or '''
- An error occurred. See the error logs for more information.
- (Turn debug on to display exception reports here)
- '''
- return_error = error_template('', msg, '')
- else:
- return_error = None
- if not reported and error_stream:
- err_report = formatter.format_text(exc_data, show_hidden_frames=True)
- err_report += '\n' + '-'*60 + '\n'
- error_stream.write(err_report)
- if extra_data:
- error_stream.write(extra_data)
- return return_error
-
-def send_report(rep, exc_data, html=True):
- try:
- rep.report(exc_data)
- except:
- output = StringIO()
- traceback.print_exc(file=output)
- if html:
- return """
- <p>Additionally an error occurred while sending the %s report:
-
- <pre>%s</pre>
- </p>""" % (
- cgi.escape(str(rep)), output.getvalue())
- else:
- return (
- "Additionally an error occurred while sending the "
- "%s report:\n%s" % (str(rep), output.getvalue()))
- else:
- return ''
-
-def error_template(head_html, exception, extra):
- return '''
- <html>
- <head>
- <title>Server Error</title>
- %s
- </head>
- <body>
- <h1>Server Error</h1>
- %s
- %s
- </body>
- </html>''' % (head_html, exception, extra)
-
-def make_error_middleware(app, global_conf, **kw):
- return ErrorMiddleware(app, global_conf=global_conf, **kw)
-
-doc_lines = ErrorMiddleware.__doc__.splitlines(True)
-for i in range(len(doc_lines)):
- if doc_lines[i].strip().startswith('Settings'):
- make_error_middleware.__doc__ = ''.join(doc_lines[i:])
- break
-del i, doc_lines
diff --git a/lib/paste/exceptions/formatter.py b/lib/paste/exceptions/formatter.py
@@ -1,564 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-Formatters for the exception data that comes from ExceptionCollector.
-"""
-# @@: TODO:
-# Use this: http://www.zope.org/Members/tino/VisualTraceback/VisualTracebackNews
-
-import cgi
-import re
-from paste.util import PySourceColor
-
-def html_quote(s):
- return cgi.escape(str(s), True)
-
-class AbstractFormatter(object):
-
- general_data_order = ['object', 'source_url']
-
- def __init__(self, show_hidden_frames=False,
- include_reusable=True,
- show_extra_data=True,
- trim_source_paths=()):
- self.show_hidden_frames = show_hidden_frames
- self.trim_source_paths = trim_source_paths
- self.include_reusable = include_reusable
- self.show_extra_data = show_extra_data
-
- def format_collected_data(self, exc_data):
- general_data = {}
- if self.show_extra_data:
- for name, value_list in exc_data.extra_data.items():
- if isinstance(name, tuple):
- importance, title = name
- else:
- importance, title = 'normal', name
- for value in value_list:
- general_data[(importance, name)] = self.format_extra_data(
- importance, title, value)
- lines = []
- frames = self.filter_frames(exc_data.frames)
- for frame in frames:
- sup = frame.supplement
- if sup:
- if sup.object:
- general_data[('important', 'object')] = self.format_sup_object(
- sup.object)
- if sup.source_url:
- general_data[('important', 'source_url')] = self.format_sup_url(
- sup.source_url)
- if sup.line:
- lines.append(self.format_sup_line_pos(sup.line, sup.column))
- if sup.expression:
- lines.append(self.format_sup_expression(sup.expression))
- if sup.warnings:
- for warning in sup.warnings:
- lines.append(self.format_sup_warning(warning))
- if sup.info:
- lines.extend(self.format_sup_info(sup.info))
- if frame.supplement_exception:
- lines.append('Exception in supplement:')
- lines.append(self.quote_long(frame.supplement_exception))
- if frame.traceback_info:
- lines.append(self.format_traceback_info(frame.traceback_info))
- filename = frame.filename
- if filename and self.trim_source_paths:
- for path, repl in self.trim_source_paths:
- if filename.startswith(path):
- filename = repl + filename[len(path):]
- break
- lines.append(self.format_source_line(filename or '?', frame))
- source = frame.get_source_line()
- long_source = frame.get_source_line(2)
- if source:
- lines.append(self.format_long_source(
- source, long_source))
- etype = exc_data.exception_type
- if not isinstance(etype, basestring):
- etype = etype.__name__
- exc_info = self.format_exception_info(
- etype,
- exc_data.exception_value)
- data_by_importance = {'important': [], 'normal': [],
- 'supplemental': [], 'extra': []}
- for (importance, name), value in general_data.items():
- data_by_importance[importance].append(
- (name, value))
- for value in data_by_importance.values():
- value.sort()
- return self.format_combine(data_by_importance, lines, exc_info)
-
- def filter_frames(self, frames):
- """
- Removes any frames that should be hidden, according to the
- values of traceback_hide, self.show_hidden_frames, and the
- hidden status of the final frame.
- """
- if self.show_hidden_frames:
- return frames
- new_frames = []
- hidden = False
- for frame in frames:
- hide = frame.traceback_hide
- # @@: It would be nice to signal a warning if an unknown
- # hide string was used, but I'm not sure where to put
- # that warning.
- if hide == 'before':
- new_frames = []
- hidden = False
- elif hide == 'before_and_this':
- new_frames = []
- hidden = False
- continue
- elif hide == 'reset':
- hidden = False
- elif hide == 'reset_and_this':
- hidden = False
- continue
- elif hide == 'after':
- hidden = True
- elif hide == 'after_and_this':
- hidden = True
- continue
- elif hide:
- continue
- elif hidden:
- continue
- new_frames.append(frame)
- if frames[-1] not in new_frames:
- # We must include the last frame; that we don't indicates
- # that the error happened where something was "hidden",
- # so we just have to show everything
- return frames
- return new_frames
-
- def pretty_string_repr(self, s):
- """
- Formats the string as a triple-quoted string when it contains
- newlines.
- """
- if '\n' in s:
- s = repr(s)
- s = s[0]*3 + s[1:-1] + s[-1]*3
- s = s.replace('\\n', '\n')
- return s
- else:
- return repr(s)
-
- def long_item_list(self, lst):
- """
- Returns true if the list contains items that are long, and should
- be more nicely formatted.
- """
- how_many = 0
- for item in lst:
- if len(repr(item)) > 40:
- how_many += 1
- if how_many >= 3:
- return True
- return False
-
-class TextFormatter(AbstractFormatter):
-
- def quote(self, s):
- return s
- def quote_long(self, s):
- return s
- def emphasize(self, s):
- return s
- def format_sup_object(self, obj):
- return 'In object: %s' % self.emphasize(self.quote(repr(obj)))
- def format_sup_url(self, url):
- return 'URL: %s' % self.quote(url)
- def format_sup_line_pos(self, line, column):
- if column:
- return self.emphasize('Line %i, Column %i' % (line, column))
- else:
- return self.emphasize('Line %i' % line)
- def format_sup_expression(self, expr):
- return self.emphasize('In expression: %s' % self.quote(expr))
- def format_sup_warning(self, warning):
- return 'Warning: %s' % self.quote(warning)
- def format_sup_info(self, info):
- return [self.quote_long(info)]
- def format_source_line(self, filename, frame):
- return 'File %r, line %s in %s' % (
- filename, frame.lineno or '?', frame.name or '?')
- def format_long_source(self, source, long_source):
- return self.format_source(source)
- def format_source(self, source_line):
- return ' ' + self.quote(source_line.strip())
- def format_exception_info(self, etype, evalue):
- return self.emphasize(
- '%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)
- for name in 'normal', 'supplemental', 'extra':
- lines.extend([value for n, value in data_by_importance[name]])
- return self.format_combine_lines(lines)
-
- def format_combine_lines(self, lines):
- return '\n'.join(lines)
-
- def format_extra_data(self, importance, title, value):
- if isinstance(value, str):
- s = self.pretty_string_repr(value)
- if '\n' in s:
- return '%s:\n%s' % (title, s)
- else:
- return '%s: %s' % (title, s)
- elif isinstance(value, dict):
- lines = ['\n', title, '-'*len(title)]
- items = value.items()
- items.sort()
- for n, v in items:
- try:
- v = repr(v)
- except Exception, e:
- v = 'Cannot display: %s' % e
- v = truncate(v)
- lines.append(' %s: %s' % (n, v))
- return '\n'.join(lines)
- elif (isinstance(value, (list, tuple))
- and self.long_item_list(value)):
- parts = [truncate(repr(v)) for v in value]
- return '%s: [\n %s]' % (
- title, ',\n '.join(parts))
- else:
- return '%s: %s' % (title, truncate(repr(value)))
-
-class HTMLFormatter(TextFormatter):
-
- def quote(self, s):
- return html_quote(s)
- def quote_long(self, s):
- return '<pre>%s</pre>' % self.quote(s)
- def emphasize(self, s):
- return '<b>%s</b>' % s
- def format_sup_url(self, url):
- return 'URL: <a href="%s">%s</a>' % (url, url)
- def format_combine_lines(self, lines):
- return '<br>\n'.join(lines)
- def format_source_line(self, filename, frame):
- name = self.quote(frame.name or '?')
- return 'Module <span class="module" title="%s">%s</span>:<b>%s</b> in <code>%s</code>' % (
- filename, frame.modname or '?', frame.lineno or '?',
- name)
- return 'File %r, line %s in <tt>%s</tt>' % (
- filename, frame.lineno, name)
- def format_long_source(self, source, long_source):
- q_long_source = str2html(long_source, False, 4, True)
- q_source = str2html(source, True, 0, False)
- return ('<code style="display: none" class="source" source-type="long"><a class="switch_source" onclick="return switch_source(this, \'long\')" href="#"><< </a>%s</code>'
- '<code class="source" source-type="short"><a onclick="return switch_source(this, \'short\')" class="switch_source" href="#">>> </a>%s</code>'
- % (q_long_source,
- q_source))
- def format_source(self, source_line):
- return ' <code class="source">%s</code>' % self.quote(source_line.strip())
- def format_traceback_info(self, info):
- return '<pre>%s</pre>' % self.quote(info)
-
- def format_extra_data(self, importance, title, value):
- if isinstance(value, str):
- s = self.pretty_string_repr(value)
- if '\n' in s:
- return '%s:<br><pre>%s</pre>' % (title, self.quote(s))
- else:
- return '%s: <tt>%s</tt>' % (title, self.quote(s))
- elif isinstance(value, dict):
- return self.zebra_table(title, value)
- elif (isinstance(value, (list, tuple))
- and self.long_item_list(value)):
- return '%s: <tt>[<br>\n %s]</tt>' % (
- title, ',<br> '.join(map(self.quote, map(repr, value))))
- else:
- return '%s: <tt>%s</tt>' % (title, self.quote(repr(value)))
-
- 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)
- for name in 'normal', 'supplemental':
- lines.extend([value for n, value in data_by_importance[name]])
- if data_by_importance['extra']:
- lines.append(
- '<script type="text/javascript">\nshow_button(\'extra_data\', \'extra data\');\n</script>\n' +
- '<div id="extra_data" class="hidden-data">\n')
- lines.extend([value for n, value in data_by_importance['extra']])
- lines.append('</div>')
- text = self.format_combine_lines(lines)
- if self.include_reusable:
- return error_css + hide_display_js + text
- else:
- # Usually because another error is already on this page,
- # and so the js & CSS are unneeded
- return text
-
- def zebra_table(self, title, rows, table_class="variables"):
- if isinstance(rows, dict):
- rows = rows.items()
- rows.sort()
- table = ['<table class="%s">' % table_class,
- '<tr class="header"><th colspan="2">%s</th></tr>'
- % self.quote(title)]
- odd = False
- for name, value in rows:
- try:
- value = repr(value)
- except Exception, e:
- value = 'Cannot print: %s' % e
- odd = not odd
- table.append(
- '<tr class="%s"><td>%s</td>'
- % (odd and 'odd' or 'even', self.quote(name)))
- table.append(
- '<td><tt>%s</tt></td></tr>'
- % make_wrappable(self.quote(truncate(value))))
- table.append('</table>')
- return '\n'.join(table)
-
-hide_display_js = r'''
-<script type="text/javascript">
-function hide_display(id) {
- var el = document.getElementById(id);
- if (el.className == "hidden-data") {
- el.className = "";
- return true;
- } else {
- el.className = "hidden-data";
- return false;
- }
-}
-document.write('<style type="text/css">\n');
-document.write('.hidden-data {display: none}\n');
-document.write('</style>\n');
-function show_button(toggle_id, name) {
- document.write('<a href="#' + toggle_id
- + '" onclick="javascript:hide_display(\'' + toggle_id
- + '\')" class="button">' + name + '</a><br>');
-}
-
-function switch_source(el, hide_type) {
- while (el) {
- if (el.getAttribute &&
- el.getAttribute('source-type') == hide_type) {
- break;
- }
- el = el.parentNode;
- }
- if (! el) {
- return false;
- }
- el.style.display = 'none';
- if (hide_type == 'long') {
- while (el) {
- if (el.getAttribute &&
- el.getAttribute('source-type') == 'short') {
- break;
- }
- el = el.nextSibling;
- }
- } else {
- while (el) {
- if (el.getAttribute &&
- el.getAttribute('source-type') == 'long') {
- break;
- }
- el = el.previousSibling;
- }
- }
- if (el) {
- el.style.display = '';
- }
- return false;
-}
-
-</script>'''
-
-
-error_css = """
-<style type="text/css">
-body {
- font-family: Helvetica, sans-serif;
-}
-
-table {
- width: 100%;
-}
-
-tr.header {
- background-color: #006;
- color: #fff;
-}
-
-tr.even {
- background-color: #ddd;
-}
-
-table.variables td {
- vertical-align: top;
- overflow: auto;
-}
-
-a.button {
- background-color: #ccc;
- border: 2px outset #aaa;
- color: #000;
- text-decoration: none;
-}
-
-a.button:hover {
- background-color: #ddd;
-}
-
-code.source {
- color: #006;
-}
-
-a.switch_source {
- color: #090;
- text-decoration: none;
-}
-
-a.switch_source:hover {
- background-color: #ddd;
-}
-
-.source-highlight {
- background-color: #ff9;
-}
-
-</style>
-"""
-
-def format_html(exc_data, include_hidden_frames=False, **ops):
- if not include_hidden_frames:
- return HTMLFormatter(**ops).format_collected_data(exc_data)
- short_er = format_html(exc_data, show_hidden_frames=False, **ops)
- # @@: This should have a way of seeing if the previous traceback
- # was actually trimmed at all
- ops['include_reusable'] = False
- ops['show_extra_data'] = False
- long_er = format_html(exc_data, show_hidden_frames=True, **ops)
- text_er = format_text(exc_data, show_hidden_frames=True, **ops)
- return """
- %s
- <br>
- <script type="text/javascript">
- show_button('full_traceback', 'full traceback')
- </script>
- <div id="full_traceback" class="hidden-data">
- %s
- </div>
- <br>
- <script type="text/javascript">
- show_button('text_version', 'text version')
- </script>
- <div id="text_version" class="hidden-data">
- <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)
-
-whitespace_re = re.compile(r' +')
-pre_re = re.compile(r'</?pre.*?>')
-error_re = re.compile(r'<h3>ERROR: .*?</h3>')
-
-def str2html(src, strip=False, indent_subsequent=0,
- highlight_inner=False):
- """
- Convert a string to HTML. Try to be really safe about it,
- returning a quoted version of the string if nothing else works.
- """
- try:
- return _str2html(src, strip=strip,
- indent_subsequent=indent_subsequent,
- highlight_inner=highlight_inner)
- except:
- return html_quote(src)
-
-def _str2html(src, strip=False, indent_subsequent=0,
- highlight_inner=False):
- if strip:
- src = src.strip()
- orig_src = src
- try:
- src = PySourceColor.str2html(src, form='snip')
- src = error_re.sub('', src)
- src = pre_re.sub('', src)
- src = re.sub(r'^[\n\r]{0,1}', '', src)
- src = re.sub(r'[\n\r]{0,1}$', '', src)
- except:
- src = html_quote(orig_src)
- lines = src.splitlines()
- if len(lines) == 1:
- return lines[0]
- indent = ' '*indent_subsequent
- for i in range(1, len(lines)):
- lines[i] = indent+lines[i]
- if highlight_inner and i == len(lines)/2:
- lines[i] = '<span class="source-highlight">%s</span>' % lines[i]
- src = '<br>\n'.join(lines)
- src = whitespace_re.sub(
- lambda m: ' '*(len(m.group(0))-1) + ' ', src)
- return src
-
-def truncate(string, limit=1000):
- """
- Truncate the string to the limit number of
- characters
- """
- if len(string) > limit:
- return string[:limit-20]+'...'+string[-17:]
- else:
- return string
-
-def make_wrappable(html, wrap_limit=60,
- split_on=';?&@!$#-/\\"\''):
- # Currently using <wbr>, maybe should use ​
- # http://www.cs.tut.fi/~jkorpela/html/nobr.html
- if len(html) <= wrap_limit:
- return html
- words = html.split()
- new_words = []
- for word in words:
- wrapped_word = ''
- while len(word) > wrap_limit:
- for char in split_on:
- if char in word:
- first, rest = word.split(char, 1)
- wrapped_word += first+char+'<wbr>'
- word = rest
- break
- else:
- for i in range(0, len(word), wrap_limit):
- wrapped_word += word[i:i+wrap_limit]+'<wbr>'
- word = ''
- wrapped_word += word
- new_words.append(wrapped_word)
- return ' '.join(new_words)
-
-def make_pre_wrappable(html, wrap_limit=60,
- split_on=';?&@!$#-/\\"\''):
- """
- Like ``make_wrappable()`` but intended for text that will
- go in a ``<pre>`` block, so wrap on a line-by-line basis.
- """
- lines = html.splitlines()
- new_lines = []
- for line in lines:
- if len(line) > wrap_limit:
- for char in split_on:
- if char in line:
- parts = line.split(char)
- line = '<wbr>'.join(parts)
- break
- new_lines.append(line)
- return '\n'.join(lines)
diff --git a/lib/paste/exceptions/reporter.py b/lib/paste/exceptions/reporter.py
@@ -1,142 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-from email.MIMEText import MIMEText
-from email.MIMEMultipart import MIMEMultipart
-import smtplib
-import time
-try:
- from socket import sslerror
-except ImportError:
- sslerror = None
-from paste.exceptions import formatter
-
-class Reporter(object):
-
- def __init__(self, **conf):
- for name, value in conf.items():
- if not hasattr(self, name):
- raise TypeError(
- "The keyword argument %s was not expected"
- % name)
- setattr(self, name, value)
- self.check_params()
-
- def check_params(self):
- pass
-
- def format_date(self, exc_data):
- return time.strftime('%c', exc_data.date)
-
- def format_html(self, exc_data, **kw):
- return formatter.format_html(exc_data, **kw)
-
- def format_text(self, exc_data, **kw):
- return formatter.format_text(exc_data, **kw)
-
-class EmailReporter(Reporter):
-
- to_addresses = None
- from_address = None
- smtp_server = 'localhost'
- smtp_username = None
- smtp_password = None
- smtp_use_tls = False
- subject_prefix = ''
-
- def report(self, exc_data):
- msg = self.assemble_email(exc_data)
- server = smtplib.SMTP(self.smtp_server)
- if self.smtp_use_tls:
- server.ehlo()
- server.starttls()
- server.ehlo()
- if self.smtp_username and self.smtp_password:
- server.login(self.smtp_username, self.smtp_password)
- server.sendmail(self.from_address,
- self.to_addresses, msg.as_string())
- try:
- server.quit()
- except sslerror:
- # sslerror is raised in tls connections on closing sometimes
- pass
-
- def check_params(self):
- if not self.to_addresses:
- raise ValueError("You must set to_addresses")
- if not self.from_address:
- raise ValueError("You must set from_address")
- if isinstance(self.to_addresses, (str, unicode)):
- self.to_addresses = [self.to_addresses]
-
- def assemble_email(self, exc_data):
- short_html_version = self.format_html(
- exc_data, show_hidden_frames=False)
- long_html_version = self.format_html(
- exc_data, show_hidden_frames=True)
- text_version = self.format_text(
- exc_data, show_hidden_frames=False)
- msg = MIMEMultipart()
- msg.set_type('multipart/alternative')
- msg.preamble = msg.epilogue = ''
- text_msg = MIMEText(text_version)
- text_msg.set_type('text/plain')
- text_msg.set_param('charset', 'ASCII')
- msg.attach(text_msg)
- html_msg = MIMEText(short_html_version)
- html_msg.set_type('text/html')
- # @@: Correct character set?
- html_msg.set_param('charset', 'UTF-8')
- html_long = MIMEText(long_html_version)
- html_long.set_type('text/html')
- html_long.set_param('charset', 'UTF-8')
- msg.attach(html_msg)
- msg.attach(html_long)
- subject = '%s: %s' % (exc_data.exception_type,
- formatter.truncate(str(exc_data.exception_value)))
- msg['Subject'] = self.subject_prefix + subject
- msg['From'] = self.from_address
- msg['To'] = ', '.join(self.to_addresses)
- return msg
-
-class LogReporter(Reporter):
-
- filename = None
- show_hidden_frames = True
-
- def check_params(self):
- assert self.filename is not None, (
- "You must give a filename")
-
- def report(self, exc_data):
- text = self.format_text(
- exc_data, show_hidden_frames=self.show_hidden_frames)
- f = open(self.filename, 'a')
- try:
- f.write(text + '\n' + '-'*60 + '\n')
- finally:
- f.close()
-
-class FileReporter(Reporter):
-
- file = None
- show_hidden_frames = True
-
- def check_params(self):
- assert self.file is not None, (
- "You must give a file object")
-
- def report(self, exc_data):
- text = self.format_text(
- exc_data, show_hidden_frames=self.show_hidden_frames)
- print text
- self.file.write(text + '\n' + '-'*60 + '\n')
-
-class WSGIAppReporter(Reporter):
-
- def __init__(self, exc_data):
- self.exc_data = exc_data
-
- def __call__(self, environ, start_response):
- start_response('500 Server Error', [('Content-type', 'text/html')])
- return [formatter.format_html(self.exc_data)]
diff --git a/lib/paste/exceptions/serial_number_generator.py b/lib/paste/exceptions/serial_number_generator.py
@@ -1,123 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-Creates a human-readable identifier, using numbers and digits,
-avoiding ambiguous numbers and letters. hash_identifier can be used
-to create compact representations that are unique for a certain string
-(or concatenation of strings)
-"""
-
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
-
-good_characters = "23456789abcdefghjkmnpqrtuvwxyz"
-
-base = len(good_characters)
-
-def make_identifier(number):
- """
- Encodes a number as an identifier.
- """
- if not isinstance(number, (int, long)):
- raise ValueError(
- "You can only make identifiers out of integers (not %r)"
- % number)
- if number < 0:
- raise ValueError(
- "You cannot make identifiers out of negative numbers: %r"
- % number)
- result = []
- while number:
- next = number % base
- result.append(good_characters[next])
- # Note, this depends on integer rounding of results:
- number = number / base
- return ''.join(result)
-
-def hash_identifier(s, length, pad=True, hasher=md5, prefix='',
- group=None, upper=False):
- """
- Hashes the string (with the given hashing module), then turns that
- hash into an identifier of the given length (using modulo to
- reduce the length of the identifier). If ``pad`` is False, then
- the minimum-length identifier will be used; otherwise the
- identifier will be padded with 0's as necessary.
-
- ``prefix`` will be added last, and does not count towards the
- target length. ``group`` will group the characters with ``-`` in
- the given lengths, and also does not count towards the target
- length. E.g., ``group=4`` will cause a identifier like
- ``a5f3-hgk3-asdf``. Grouping occurs before the prefix.
- """
- if not callable(hasher):
- # Accept sha/md5 modules as well as callables
- hasher = hasher.new
- if length > 26 and hasher is md5:
- raise ValueError, (
- "md5 cannot create hashes longer than 26 characters in "
- "length (you gave %s)" % length)
- if isinstance(s, unicode):
- s = s.encode('utf-8')
- h = hasher(str(s))
- bin_hash = h.digest()
- modulo = base ** length
- number = 0
- for c in list(bin_hash):
- number = (number * 256 + ord(c)) % modulo
- ident = make_identifier(number)
- if pad:
- ident = good_characters[0]*(length-len(ident)) + ident
- if group:
- parts = []
- while ident:
- parts.insert(0, ident[-group:])
- ident = ident[:-group]
- ident = '-'.join(parts)
- if upper:
- ident = ident.upper()
- return prefix + ident
-
-# doctest tests:
-__test__ = {
- 'make_identifier': """
- >>> make_identifier(0)
- ''
- >>> make_identifier(1000)
- 'c53'
- >>> make_identifier(-100)
- Traceback (most recent call last):
- ...
- ValueError: You cannot make identifiers out of negative numbers: -100
- >>> make_identifier('test')
- Traceback (most recent call last):
- ...
- ValueError: You can only make identifiers out of integers (not 'test')
- >>> make_identifier(1000000000000)
- 'c53x9rqh3'
- """,
- 'hash_identifier': """
- >>> hash_identifier(0, 5)
- 'cy2dr'
- >>> hash_identifier(0, 10)
- 'cy2dr6rg46'
- >>> hash_identifier('this is a test of a long string', 5)
- 'awatu'
- >>> hash_identifier(0, 26)
- 'cy2dr6rg46cx8t4w2f3nfexzk4'
- >>> hash_identifier(0, 30)
- Traceback (most recent call last):
- ...
- ValueError: md5 cannot create hashes longer than 26 characters in length (you gave 30)
- >>> hash_identifier(0, 10, group=4)
- 'cy-2dr6-rg46'
- >>> hash_identifier(0, 10, group=4, upper=True, prefix='M-')
- 'M-CY-2DR6-RG46'
- """}
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
-
diff --git a/lib/paste/fileapp.py b/lib/paste/fileapp.py
@@ -1,349 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# (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
-"""
-This module handles sending static content such as in-memory data or
-files. At this time it has cache helpers and understands the
-if-modified-since request header.
-"""
-
-import os, time, mimetypes, zipfile, tarfile
-from paste.httpexceptions import *
-from paste.httpheaders import *
-
-CACHE_SIZE = 4096
-BLOCK_SIZE = 4096 * 16
-
-__all__ = ['DataApp', 'FileApp', 'DirectoryApp', 'ArchiveStore']
-
-class DataApp(object):
- """
- Returns an application that will send content in a single chunk,
- this application has support for setting cache-control and for
- responding to conditional (or HEAD) requests.
-
- Constructor Arguments:
-
- ``content`` the content being sent to the client
-
- ``headers`` the headers to send /w the response
-
- The remaining ``kwargs`` correspond to headers, where the
- underscore is replaced with a dash. These values are only
- added to the headers if they are not already provided; thus,
- they can be used for default values. Examples include, but
- are not limited to:
-
- ``content_type``
- ``content_encoding``
- ``content_location``
-
- ``cache_control()``
-
- This method provides validated construction of the ``Cache-Control``
- header as well as providing for automated filling out of the
- ``EXPIRES`` header for HTTP/1.0 clients.
-
- ``set_content()``
-
- This method provides a mechanism to set the content after the
- application has been constructed. This method does things
- like changing ``Last-Modified`` and ``Content-Length`` headers.
-
- """
-
- allowed_methods = ('GET', 'HEAD')
-
- def __init__(self, content, headers=None, allowed_methods=None,
- **kwargs):
- assert isinstance(headers, (type(None), list))
- self.expires = None
- self.content = None
- self.content_length = None
- self.last_modified = 0
- if allowed_methods is not None:
- self.allowed_methods = allowed_methods
- self.headers = headers or []
- for (k, v) in kwargs.items():
- header = get_header(k)
- header.update(self.headers, v)
- ACCEPT_RANGES.update(self.headers, bytes=True)
- if not CONTENT_TYPE(self.headers):
- CONTENT_TYPE.update(self.headers)
- if content is not None:
- self.set_content(content)
-
- def cache_control(self, **kwargs):
- self.expires = CACHE_CONTROL.apply(self.headers, **kwargs) or None
- return self
-
- def set_content(self, content, last_modified=None):
- assert content is not None
- if last_modified is None:
- self.last_modified = time.time()
- else:
- self.last_modified = last_modified
- self.content = content
- self.content_length = len(content)
- LAST_MODIFIED.update(self.headers, time=self.last_modified)
- return self
-
- def content_disposition(self, **kwargs):
- CONTENT_DISPOSITION.apply(self.headers, **kwargs)
- return self
-
- def __call__(self, environ, start_response):
- method = environ['REQUEST_METHOD'].upper()
- if method not in self.allowed_methods:
- exc = HTTPMethodNotAllowed(
- 'You cannot %s a file' % method,
- headers=[('Allow', ','.join(self.allowed_methods))])
- return exc(environ, start_response)
- return self.get(environ, start_response)
-
- def calculate_etag(self):
- return str(self.last_modified) + '-' + str(self.content_length)
-
- def get(self, environ, start_response):
- headers = self.headers[:]
- current_etag = self.calculate_etag()
- ETAG.update(headers, current_etag)
- if self.expires is not None:
- EXPIRES.update(headers, delta=self.expires)
-
- try:
- client_etags = IF_NONE_MATCH.parse(environ)
- if client_etags:
- for etag in client_etags:
- if etag == current_etag or etag == '*':
- # horribly inefficient, n^2 performance, yuck!
- for head in list_headers(entity=True):
- head.delete(headers)
- start_response('304 Not Modified', headers)
- return ['']
- except HTTPBadRequest, exce:
- return exce.wsgi_application(environ, start_response)
-
- # If we get If-None-Match and If-Modified-Since, and
- # If-None-Match doesn't match, then we should not try to
- # figure out If-Modified-Since (which has 1-second granularity
- # and just isn't as accurate)
- if not client_etags:
- try:
- client_clock = IF_MODIFIED_SINCE.parse(environ)
- if 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
- except HTTPBadRequest, exce:
- return exce.wsgi_application(environ, start_response)
-
- (lower, upper) = (0, self.content_length - 1)
- range = RANGE.parse(environ)
- if range and 'bytes' == range[0] and 1 == len(range[1]):
- (lower, upper) = range[1][0]
- upper = upper or (self.content_length - 1)
- if upper >= self.content_length or lower > upper:
- return HTTPRequestRangeNotSatisfiable((
- "Range request was made beyond the end of the content,\r\n"
- "which is %s long.\r\n Range: %s\r\n") % (
- self.content_length, RANGE(environ))
- ).wsgi_application(environ, start_response)
-
- content_length = upper - lower + 1
- CONTENT_RANGE.update(headers, first_byte=lower, last_byte=upper,
- total_length = self.content_length)
- CONTENT_LENGTH.update(headers, content_length)
- if content_length == self.content_length:
- start_response('200 OK', headers)
- else:
- start_response('206 Partial Content', headers)
- if self.content is not None:
- return [self.content[lower:upper+1]]
- return (lower, content_length)
-
-class FileApp(DataApp):
- """
- Returns an application that will send the file at the given
- filename. Adds a mime type based on ``mimetypes.guess_type()``.
- See DataApp for the arguments beyond ``filename``.
- """
-
- def __init__(self, filename, headers=None, **kwargs):
- self.filename = filename
- content_type, content_encoding = self.guess_type()
- if content_type and 'content_type' not in kwargs:
- kwargs['content_type'] = content_type
- if content_encoding and 'content_encoding' not in kwargs:
- kwargs['content_encoding'] = content_encoding
- DataApp.__init__(self, None, headers, **kwargs)
-
- def guess_type(self):
- return mimetypes.guess_type(self.filename)
-
- def update(self, force=False):
- stat = os.stat(self.filename)
- if not force and stat.st_mtime == self.last_modified:
- return
- self.last_modified = stat.st_mtime
- if stat.st_size < CACHE_SIZE:
- fh = open(self.filename,"rb")
- self.set_content(fh.read(), stat.st_mtime)
- fh.close()
- else:
- self.content = None
- self.content_length = stat.st_size
- # This is updated automatically if self.set_content() is
- # called
- LAST_MODIFIED.update(self.headers, time=self.last_modified)
-
- def get(self, environ, start_response):
- is_head = environ['REQUEST_METHOD'].upper() == 'HEAD'
- if 'max-age=0' in CACHE_CONTROL(environ).lower():
- self.update(force=True) # RFC 2616 13.2.6
- else:
- self.update()
- if not self.content:
- if not os.path.exists(self.filename):
- exc = HTTPNotFound(
- 'The resource does not exist',
- comment="No file at %r" % self.filename)
- return exc(environ, start_response)
- try:
- file = open(self.filename, 'rb')
- except (IOError, OSError), e:
- exc = HTTPForbidden(
- 'You are not permitted to view this file (%s)' % e)
- return exc.wsgi_application(
- environ, start_response)
- retval = DataApp.get(self, environ, start_response)
- if isinstance(retval, list):
- # cached content, exception, or not-modified
- if is_head:
- return ['']
- return retval
- (lower, content_length) = retval
- if is_head:
- return ['']
- file.seek(lower)
- file_wrapper = environ.get('wsgi.file_wrapper', None)
- if file_wrapper:
- return file_wrapper(file, BLOCK_SIZE)
- else:
- return _FileIter(file, size=content_length)
-
-class _FileIter(object):
-
- def __init__(self, file, block_size=None, size=None):
- self.file = file
- self.size = size
- self.block_size = block_size or BLOCK_SIZE
-
- def __iter__(self):
- return self
-
- def next(self):
- chunk_size = self.block_size
- if self.size is not None:
- if chunk_size > self.size:
- chunk_size = self.size
- self.size -= chunk_size
- data = self.file.read(chunk_size)
- if not data:
- raise StopIteration
- return data
-
- def close(self):
- self.file.close()
-
-
-class DirectoryApp(object):
- """
- Returns an application that dispatches requests to corresponding FileApps based on PATH_INFO.
- FileApp instances are cached. This app makes sure not to serve any files that are not in a subdirectory.
- To customize FileApp creation override ``DirectoryApp.make_fileapp``
- """
-
- def __init__(self, path):
- self.path = os.path.abspath(path)
- if not self.path.endswith(os.path.sep):
- self.path += os.path.sep
- assert os.path.isdir(self.path)
- self.cached_apps = {}
-
- make_fileapp = FileApp
-
- def __call__(self, environ, start_response):
- path_info = environ['PATH_INFO']
- app = self.cached_apps.get(path_info)
- if app is None:
- path = os.path.join(self.path, path_info.lstrip('/'))
- if not os.path.normpath(path).startswith(self.path):
- app = HTTPForbidden()
- elif os.path.isfile(path):
- app = self.make_fileapp(path)
- self.cached_apps[path_info] = app
- else:
- app = HTTPNotFound(comment=path)
- return app(environ, start_response)
-
-
-class ArchiveStore(object):
- """
- Returns an application that serves up a DataApp for items requested
- in a given zip or tar archive.
-
- Constructor Arguments:
-
- ``filepath`` the path to the archive being served
-
- ``cache_control()``
-
- This method provides validated construction of the ``Cache-Control``
- header as well as providing for automated filling out of the
- ``EXPIRES`` header for HTTP/1.0 clients.
- """
-
- def __init__(self, filepath):
- if zipfile.is_zipfile(filepath):
- self.archive = zipfile.ZipFile(filepath,"r")
- elif tarfile.is_tarfile(filepath):
- self.archive = tarfile.TarFileCompat(filepath,"r")
- else:
- raise AssertionError("filepath '%s' is not a zip or tar " % filepath)
- self.expires = None
- self.last_modified = time.time()
- self.cache = {}
-
- def cache_control(self, **kwargs):
- self.expires = CACHE_CONTROL.apply(self.headers, **kwargs) or None
- return self
-
- def __call__(self, environ, start_response):
- path = environ.get("PATH_INFO","")
- if path.startswith("/"):
- path = path[1:]
- application = self.cache.get(path)
- if application:
- return application(environ, start_response)
- try:
- info = self.archive.getinfo(path)
- except KeyError:
- exc = HTTPNotFound("The file requested, '%s', was not found." % path)
- return exc.wsgi_application(environ, start_response)
- if info.filename.endswith("/"):
- exc = HTTPNotFound("Path requested, '%s', is not a file." % path)
- return exc.wsgi_application(environ, start_response)
- content_type, content_encoding = mimetypes.guess_type(info.filename)
- app = DataApp(None, content_type = content_type,
- content_encoding = content_encoding)
- app.set_content(self.archive.read(path),
- time.mktime(info.date_time + (0,0,0)))
- self.cache[path] = app
- app.expires = self.expires
- return app(environ, start_response)
-
diff --git a/lib/paste/fixture.py b/lib/paste/fixture.py
@@ -1,1723 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Routines for testing WSGI applications.
-
-Most interesting is the `TestApp <class-paste.fixture.TestApp.html>`_
-for testing WSGI applications, and the `TestFileEnvironment
-<class-paste.fixture.TestFileEnvironment.html>`_ class for testing the
-effects of command-line scripts.
-"""
-
-import sys
-import random
-import urllib
-import urlparse
-import mimetypes
-import time
-import cgi
-import os
-import shutil
-import smtplib
-import shlex
-from Cookie import BaseCookie
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-import re
-try:
- import subprocess
-except ImportError:
- from paste.util import subprocess24 as subprocess
-
-from paste import wsgilib
-from paste import lint
-from paste.response import HeaderDict
-
-def tempnam_no_warning(*args):
- """
- An os.tempnam with the warning turned off, because sometimes
- you just need to use this and don't care about the stupid
- security warning.
- """
- return os.tempnam(*args)
-
-class NoDefault(object):
- pass
-
-def sorted(l):
- l = list(l)
- l.sort()
- return l
-
-class Dummy_smtplib(object):
-
- existing = None
-
- def __init__(self, server):
- import warnings
- warnings.warn(
- 'Dummy_smtplib is not maintained and is deprecated',
- DeprecationWarning, 2)
- assert not self.existing, (
- "smtplib.SMTP() called again before Dummy_smtplib.existing.reset() "
- "called.")
- self.server = server
- self.open = True
- self.__class__.existing = self
-
- def quit(self):
- assert self.open, (
- "Called %s.quit() twice" % self)
- self.open = False
-
- def sendmail(self, from_address, to_addresses, msg):
- self.from_address = from_address
- self.to_addresses = to_addresses
- self.message = msg
-
- def install(cls):
- smtplib.SMTP = cls
-
- install = classmethod(install)
-
- def reset(self):
- assert not self.open, (
- "SMTP connection not quit")
- self.__class__.existing = None
-
-class AppError(Exception):
- pass
-
-class TestApp(object):
-
- # for py.test
- disabled = True
-
- def __init__(self, app, namespace=None, relative_to=None,
- extra_environ=None, pre_request_hook=None,
- post_request_hook=None):
- """
- Wraps a WSGI application in a more convenient interface for
- testing.
-
- ``app`` may be an application, or a Paste Deploy app
- URI, like ``'config:filename.ini#test'``.
-
- ``namespace`` is a dictionary that will be written to (if
- provided). This can be used with doctest or some other
- system, and the variable ``res`` will be assigned everytime
- you make a request (instead of returning the request).
-
- ``relative_to`` is a directory, and filenames used for file
- uploads are calculated relative to this. Also ``config:``
- URIs that aren't absolute.
-
- ``extra_environ`` is a dictionary of values that should go
- into the environment for each request. These can provide a
- communication channel with the application.
-
- ``pre_request_hook`` is a function to be called prior to
- making requests (such as ``post`` or ``get``). This function
- must take one argument (the instance of the TestApp).
-
- ``post_request_hook`` is a function, similar to
- ``pre_request_hook``, to be called after requests are made.
- """
- if isinstance(app, (str, unicode)):
- from paste.deploy import loadapp
- # @@: Should pick up relative_to from calling module's
- # __file__
- app = loadapp(app, relative_to=relative_to)
- self.app = app
- self.namespace = namespace
- self.relative_to = relative_to
- if extra_environ is None:
- extra_environ = {}
- self.extra_environ = extra_environ
- self.pre_request_hook = pre_request_hook
- self.post_request_hook = post_request_hook
- self.reset()
-
- def reset(self):
- """
- Resets the state of the application; currently just clears
- saved cookies.
- """
- self.cookies = {}
-
- def _make_environ(self):
- environ = self.extra_environ.copy()
- environ['paste.throw_errors'] = True
- return environ
-
- def get(self, url, params=None, headers=None, extra_environ=None,
- status=None, expect_errors=False):
- """
- Get the given url (well, actually a path like
- ``'/page.html'``).
-
- ``params``:
- A query string, or a dictionary that will be encoded
- into a query string. You may also include a query
- string on the ``url``.
-
- ``headers``:
- A dictionary of extra headers to send.
-
- ``extra_environ``:
- A dictionary of environmental variables that should
- be added to the request.
-
- ``status``:
- The integer status code you expect (if not 200 or 3xx).
- If you expect a 404 response, for instance, you must give
- ``status=404`` or it will be an error. You can also give
- a wildcard, like ``'3*'`` or ``'*'``.
-
- ``expect_errors``:
- If this is not true, then if anything is written to
- ``wsgi.errors`` it will be an error. If it is true, then
- non-200/3xx responses are also okay.
-
- Returns a `response object
- <class-paste.fixture.TestResponse.html>`_
- """
- if extra_environ is None:
- extra_environ = {}
- # Hide from py.test:
- __tracebackhide__ = True
- if params:
- if not isinstance(params, (str, unicode)):
- params = urllib.urlencode(params, doseq=True)
- if '?' in url:
- url += '&'
- else:
- url += '?'
- url += params
- environ = self._make_environ()
- url = str(url)
- if '?' in url:
- url, environ['QUERY_STRING'] = url.split('?', 1)
- else:
- environ['QUERY_STRING'] = ''
- self._set_headers(headers, environ)
- environ.update(extra_environ)
- 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,
- status=None, upload_files=None, expect_errors=False):
- """
- Do a generic request.
- """
- if headers is None:
- headers = {}
- if extra_environ is None:
- extra_environ = {}
- environ = self._make_environ()
- # @@: Should this be all non-strings?
- if isinstance(params, (list, tuple, dict)):
- params = urllib.urlencode(params)
- if hasattr(params, 'items'):
- # Some other multi-dict like format
- params = urllib.urlencode(params.items())
- if upload_files:
- params = cgi.parse_qsl(params, keep_blank_values=True)
- content_type, params = self.encode_multipart(
- params, upload_files)
- environ['CONTENT_TYPE'] = content_type
- elif params:
- environ.setdefault('CONTENT_TYPE', 'application/x-www-form-urlencoded')
- if '?' in url:
- url, environ['QUERY_STRING'] = url.split('?', 1)
- else:
- environ['QUERY_STRING'] = ''
- environ['CONTENT_LENGTH'] = str(len(params))
- environ['REQUEST_METHOD'] = method
- environ['wsgi.input'] = StringIO(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,
- status=None, upload_files=None, expect_errors=False):
- """
- Do a POST request. Very like the ``.get()`` method.
- ``params`` are put in the body of the request.
-
- ``upload_files`` is for file uploads. It should be a list of
- ``[(fieldname, filename, file_content)]``. You can also use
- just ``[(fieldname, filename)]`` and the file content will be
- read from disk.
-
- Returns a `response object
- <class-paste.fixture.TestResponse.html>`_
- """
- return self._gen_request('POST', url, params=params, headers=headers,
- extra_environ=extra_environ,status=status,
- upload_files=upload_files,
- expect_errors=expect_errors)
-
- def put(self, url, params='', headers=None, extra_environ=None,
- status=None, upload_files=None, expect_errors=False):
- """
- Do a PUT request. Very like the ``.get()`` method.
- ``params`` are put in the body of the request.
-
- ``upload_files`` is for file uploads. It should be a list of
- ``[(fieldname, filename, file_content)]``. You can also use
- just ``[(fieldname, filename)]`` and the file content will be
- read from disk.
-
- Returns a `response object
- <class-paste.fixture.TestResponse.html>`_
- """
- return self._gen_request('PUT', url, params=params, headers=headers,
- extra_environ=extra_environ,status=status,
- upload_files=upload_files,
- expect_errors=expect_errors)
-
- def delete(self, url, params='', headers=None, extra_environ=None,
- status=None, expect_errors=False):
- """
- Do a DELETE request. Very like the ``.get()`` method.
- ``params`` are put in the body of the request.
-
- Returns a `response object
- <class-paste.fixture.TestResponse.html>`_
- """
- return self._gen_request('DELETE', url, params=params, headers=headers,
- extra_environ=extra_environ,status=status,
- upload_files=None, expect_errors=expect_errors)
-
-
-
-
- def _set_headers(self, headers, environ):
- """
- Turn any headers into environ variables
- """
- if not headers:
- return
- for header, value in headers.items():
- if header.lower() == 'content-type':
- var = 'CONTENT_TYPE'
- elif header.lower() == 'content-length':
- var = 'CONTENT_LENGTH'
- else:
- var = 'HTTP_%s' % header.replace('-', '_').upper()
- environ[var] = value
-
- def encode_multipart(self, params, files):
- """
- Encodes a set of parameters (typically a name/value list) and
- a set of files (a list of (name, filename, file_body)) into a
- typical POST body, returning the (content_type, body).
- """
- boundary = '----------a_BoUnDaRy%s$' % random.random()
- lines = []
- for key, value in params:
- lines.append('--'+boundary)
- lines.append('Content-Disposition: form-data; name="%s"' % key)
- lines.append('')
- lines.append(value)
- 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"'
- % (key, filename))
- fcontent = mimetypes.guess_type(filename)[0]
- lines.append('Content-Type: %s' %
- fcontent or 'application/octet-stream')
- lines.append('')
- lines.append(value)
- lines.append('--' + boundary + '--')
- lines.append('')
- body = '\r\n'.join(lines)
- content_type = 'multipart/form-data; boundary=%s' % boundary
- return content_type, body
-
- def _get_file_info(self, file_info):
- if len(file_info) == 2:
- # It only has a filename
- filename = file_info[1]
- if self.relative_to:
- filename = os.path.join(self.relative_to, filename)
- f = open(filename, 'rb')
- content = f.read()
- f.close()
- return (file_info[0], filename, content)
- elif len(file_info) == 3:
- return file_info
- else:
- raise ValueError(
- "upload_files need to be a list of tuples of (fieldname, "
- "filename, filecontent) or (fieldname, filename); "
- "you gave: %r"
- % repr(file_info)[:100])
-
- def do_request(self, req, status):
- """
- Executes the given request (``req``), with the expected
- ``status``. Generally ``.get()`` and ``.post()`` are used
- instead.
- """
- if self.pre_request_hook:
- self.pre_request_hook(self)
- __tracebackhide__ = True
- if self.cookies:
- c = BaseCookie()
- for name, value in self.cookies.items():
- c[name] = value
- hc = '; '.join(['='.join([m.key, m.value]) for m in c.values()])
- req.environ['HTTP_COOKIE'] = hc
- req.environ['paste.testing'] = True
- req.environ['paste.testing_variables'] = {}
- app = lint.middleware(self.app)
- old_stdout = sys.stdout
- out = CaptureStdout(old_stdout)
- try:
- sys.stdout = out
- start_time = time.time()
- raise_on_wsgi_error = not req.expect_errors
- raw_res = wsgilib.raw_interactive(
- app, req.url,
- raise_on_wsgi_error=raise_on_wsgi_error,
- **req.environ)
- end_time = time.time()
- finally:
- sys.stdout = old_stdout
- sys.stderr.write(out.getvalue())
- res = self._make_response(raw_res, end_time - start_time)
- res.request = req
- for name, value in req.environ['paste.testing_variables'].items():
- if hasattr(res, name):
- raise ValueError(
- "paste.testing_variables contains the variable %r, but "
- "the response object already has an attribute by that "
- "name" % name)
- setattr(res, name, value)
- if self.namespace is not None:
- self.namespace['res'] = res
- if not req.expect_errors:
- self._check_status(status, res)
- self._check_errors(res)
- res.cookies_set = {}
- for header in res.all_headers('set-cookie'):
- c = BaseCookie(header)
- for key, morsel in c.items():
- self.cookies[key] = morsel.value
- res.cookies_set[key] = morsel.value
- if self.post_request_hook:
- self.post_request_hook(self)
- if self.namespace is None:
- # It's annoying to return the response in doctests, as it'll
- # be printed, so we only return it is we couldn't assign
- # it anywhere
- return res
-
- def _check_status(self, status, res):
- __tracebackhide__ = True
- if status == '*':
- return
- if isinstance(status, (list, tuple)):
- if res.status not in status:
- raise AppError(
- "Bad response: %s (not one of %s for %s)\n%s"
- % (res.full_status, ', '.join(map(str, status)),
- res.request.url, res.body))
- return
- if status is None:
- if res.status >= 200 and res.status < 400:
- return
- raise AppError(
- "Bad response: %s (not 200 OK or 3xx redirect for %s)\n%s"
- % (res.full_status, res.request.url,
- res.body))
- if status != res.status:
- raise AppError(
- "Bad response: %s (not %s)" % (res.full_status, status))
-
- def _check_errors(self, res):
- if res.errors:
- raise AppError(
- "Application had errors logged:\n%s" % res.errors)
-
- def _make_response(self, (status, headers, body, errors), total_time):
- return TestResponse(self, status, headers, body, errors,
- total_time)
-
-class CaptureStdout(object):
-
- def __init__(self, actual):
- self.captured = StringIO()
- self.actual = actual
-
- def write(self, s):
- self.captured.write(s)
- self.actual.write(s)
-
- def flush(self):
- self.actual.flush()
-
- def writelines(self, lines):
- for item in lines:
- self.write(item)
-
- def getvalue(self):
- return self.captured.getvalue()
-
-class TestResponse(object):
-
- # for py.test
- disabled = True
-
- """
- Instances of this class are return by `TestApp
- <class-paste.fixture.TestApp.html>`_
- """
-
- def __init__(self, test_app, status, headers, body, errors,
- total_time):
- self.test_app = test_app
- self.status = int(status.split()[0])
- self.full_status = status
- self.headers = headers
- self.header_dict = HeaderDict.fromlist(self.headers)
- self.body = body
- self.errors = errors
- self._normal_body = None
- self.time = total_time
- self._forms_indexed = None
-
- def forms__get(self):
- """
- Returns a dictionary of ``Form`` objects. Indexes are both in
- order (from zero) and by form id (if the form is given an id).
- """
- if self._forms_indexed is None:
- self._parse_forms()
- return self._forms_indexed
-
- forms = property(forms__get,
- doc="""
- A list of <form>s found on the page (instances of
- `Form <class-paste.fixture.Form.html>`_)
- """)
-
- def form__get(self):
- forms = self.forms
- if not forms:
- raise TypeError(
- "You used response.form, but no forms exist")
- if 1 in forms:
- # There is more than one form
- raise TypeError(
- "You used response.form, but more than one form exists")
- return forms[0]
-
- form = property(form__get,
- doc="""
- Returns a single `Form
- <class-paste.fixture.Form.html>`_ instance; it
- is an error if there are multiple forms on the
- page.
- """)
-
- _tag_re = re.compile(r'<(/?)([:a-z0-9_\-]*)(.*?)>', re.S|re.I)
-
- def _parse_forms(self):
- forms = self._forms_indexed = {}
- form_texts = []
- started = None
- for match in self._tag_re.finditer(self.body):
- end = match.group(1) == '/'
- tag = match.group(2).lower()
- if tag != 'form':
- continue
- if end:
- assert started, (
- "</form> unexpected at %s" % match.start())
- form_texts.append(self.body[started:match.end()])
- started = None
- else:
- assert not started, (
- "Nested form tags at %s" % match.start())
- started = match.start()
- assert not started, (
- "Danging form: %r" % self.body[started:])
- for i, text in enumerate(form_texts):
- form = Form(self, text)
- forms[i] = form
- if form.id:
- forms[form.id] = form
-
- def header(self, name, default=NoDefault):
- """
- Returns the named header; an error if there is not exactly one
- matching header (unless you give a default -- always an error
- if there is more than one header)
- """
- found = None
- for cur_name, value in self.headers:
- if cur_name.lower() == name.lower():
- assert not found, (
- "Ambiguous header: %s matches %r and %r"
- % (name, found, value))
- found = value
- if found is None:
- if default is NoDefault:
- raise KeyError(
- "No header found: %r (from %s)"
- % (name, ', '.join([n for n, v in self.headers])))
- else:
- return default
- return found
-
- def all_headers(self, name):
- """
- Gets all headers by the ``name``, returns as a list
- """
- found = []
- for cur_name, value in self.headers:
- if cur_name.lower() == name.lower():
- found.append(value)
- return found
-
- def follow(self, **kw):
- """
- If this request is a redirect, follow that redirect. It
- is an error if this is not a redirect response. Returns
- another response object.
- """
- assert self.status >= 300 and self.status < 400, (
- "You can only follow redirect responses (not %s)"
- % self.full_status)
- location = self.header('location')
- type, rest = urllib.splittype(location)
- host, path = urllib.splithost(rest)
- # @@: We should test that it's not a remote redirect
- return self.test_app.get(location, **kw)
-
- def click(self, description=None, linkid=None, href=None,
- anchor=None, index=None, verbose=False):
- """
- Click the link as described. Each of ``description``,
- ``linkid``, and ``url`` are *patterns*, meaning that they are
- either strings (regular expressions), compiled regular
- expressions (objects with a ``search`` method), or callables
- returning true or false.
-
- All the given patterns are ANDed together:
-
- * ``description`` is a pattern that matches the contents of the
- anchor (HTML and all -- everything between ``<a...>`` and
- ``</a>``)
-
- * ``linkid`` is a pattern that matches the ``id`` attribute of
- the anchor. It will receive the empty string if no id is
- given.
-
- * ``href`` is a pattern that matches the ``href`` of the anchor;
- the literal content of that attribute, not the fully qualified
- attribute.
-
- * ``anchor`` is a pattern that matches the entire anchor, with
- its contents.
-
- If more than one link matches, then the ``index`` link is
- followed. If ``index`` is not given and more than one link
- matches, or if no link matches, then ``IndexError`` will be
- raised.
-
- If you give ``verbose`` then messages will be printed about
- each link, and why it does or doesn't match. If you use
- ``app.click(verbose=True)`` you'll see a list of all the
- links.
-
- You can use multiple criteria to essentially assert multiple
- aspects about the link, e.g., where the link's destination is.
- """
- __tracebackhide__ = True
- found_html, found_desc, found_attrs = self._find_element(
- tag='a', href_attr='href',
- href_extract=None,
- content=description,
- id=linkid,
- href_pattern=href,
- html_pattern=anchor,
- index=index, verbose=verbose)
- return self.goto(found_attrs['uri'])
-
- def clickbutton(self, description=None, buttonid=None, href=None,
- button=None, index=None, verbose=False):
- """
- Like ``.click()``, except looks for link-like buttons.
- This kind of button should look like
- ``<button onclick="...location.href='url'...">``.
- """
- __tracebackhide__ = True
- found_html, found_desc, found_attrs = self._find_element(
- tag='button', href_attr='onclick',
- href_extract=re.compile(r"location\.href='(.*?)'"),
- content=description,
- id=buttonid,
- href_pattern=href,
- html_pattern=button,
- index=index, verbose=verbose)
- return self.goto(found_attrs['uri'])
-
- def _find_element(self, tag, href_attr, href_extract,
- content, id,
- href_pattern,
- html_pattern,
- index, verbose):
- content_pat = _make_pattern(content)
- id_pat = _make_pattern(id)
- href_pat = _make_pattern(href_pattern)
- html_pat = _make_pattern(html_pattern)
-
- _tag_re = re.compile(r'<%s\s+(.*?)>(.*?)</%s>' % (tag, tag),
- re.I+re.S)
-
- def printlog(s):
- if verbose:
- print s
-
- found_links = []
- total_links = 0
- for match in _tag_re.finditer(self.body):
- el_html = match.group(0)
- el_attr = match.group(1)
- el_content = match.group(2)
- attrs = _parse_attrs(el_attr)
- if verbose:
- printlog('Element: %r' % el_html)
- if not attrs.get(href_attr):
- printlog(' Skipped: no %s attribute' % href_attr)
- continue
- el_href = attrs[href_attr]
- if href_extract:
- m = href_extract.search(el_href)
- if not m:
- printlog(" Skipped: doesn't match extract pattern")
- continue
- el_href = m.group(1)
- attrs['uri'] = el_href
- if el_href.startswith('#'):
- printlog(' Skipped: only internal fragment href')
- continue
- if el_href.startswith('javascript:'):
- printlog(' Skipped: cannot follow javascript:')
- continue
- total_links += 1
- if content_pat and not content_pat(el_content):
- printlog(" Skipped: doesn't match description")
- continue
- if id_pat and not id_pat(attrs.get('id', '')):
- printlog(" Skipped: doesn't match id")
- continue
- if href_pat and not href_pat(el_href):
- printlog(" Skipped: doesn't match href")
- continue
- if html_pat and not html_pat(el_html):
- printlog(" Skipped: doesn't match html")
- continue
- printlog(" Accepted")
- found_links.append((el_html, el_content, attrs))
- if not found_links:
- raise IndexError(
- "No matching elements found (from %s possible)"
- % total_links)
- if index is None:
- if len(found_links) > 1:
- raise IndexError(
- "Multiple links match: %s"
- % ', '.join([repr(anc) for anc, d, attr in found_links]))
- found_link = found_links[0]
- else:
- try:
- found_link = found_links[index]
- except IndexError:
- raise IndexError(
- "Only %s (out of %s) links match; index %s out of range"
- % (len(found_links), total_links, index))
- return found_link
-
- def goto(self, href, method='get', **args):
- """
- Go to the (potentially relative) link ``href``, using the
- given method (``'get'`` or ``'post'``) and any extra arguments
- you want to pass to the ``app.get()`` or ``app.post()``
- methods.
-
- All hostnames and schemes will be ignored.
- """
- scheme, host, path, query, fragment = urlparse.urlsplit(href)
- # We
- scheme = host = fragment = ''
- href = urlparse.urlunsplit((scheme, host, path, query, fragment))
- href = urlparse.urljoin(self.request.full_url, href)
- method = method.lower()
- assert method in ('get', 'post'), (
- 'Only "get" or "post" are allowed for method (you gave %r)'
- % method)
- if method == 'get':
- method = self.test_app.get
- else:
- method = self.test_app.post
- return method(href, **args)
-
- _normal_body_regex = re.compile(r'[ \n\r\t]+')
-
- def normal_body__get(self):
- if self._normal_body is None:
- self._normal_body = self._normal_body_regex.sub(
- ' ', self.body)
- return self._normal_body
-
- normal_body = property(normal_body__get,
- doc="""
- Return the whitespace-normalized body
- """)
-
- def __contains__(self, s):
- """
- A response 'contains' a string if it is present in the body
- of the response. Whitespace is normalized when searching
- for a string.
- """
- if not isinstance(s, (str, unicode)):
- s = str(s)
- if isinstance(s, unicode):
- ## FIXME: we don't know that this response uses utf8:
- s = s.encode('utf8')
- return (self.body.find(s) != -1
- or self.normal_body.find(s) != -1)
-
- def mustcontain(self, *strings, **kw):
- """
- Assert that the response contains all of the strings passed
- in as arguments.
-
- Equivalent to::
-
- assert string in res
- """
- if 'no' in kw:
- no = kw['no']
- del kw['no']
- if isinstance(no, basestring):
- no = [no]
- else:
- no = []
- if kw:
- raise TypeError(
- "The only keyword argument allowed is 'no'")
- for s in strings:
- if not s in self:
- print >> sys.stderr, "Actual response (no %r):" % s
- print >> sys.stderr, self
- raise IndexError(
- "Body does not contain string %r" % s)
- for no_s in no:
- if no_s in self:
- print >> sys.stderr, "Actual response (has %r)" % s
- print >> sys.stderr, self
- raise IndexError(
- "Body contains string %r" % s)
-
- def __repr__(self):
- return '<Response %s %r>' % (self.full_status, self.body[:20])
-
- def __str__(self):
- simple_body = '\n'.join([l for l in self.body.splitlines()
- if l.strip()])
- return 'Response: %s\n%s\n%s' % (
- self.status,
- '\n'.join(['%s: %s' % (n, v) for n, v in self.headers]),
- simple_body)
-
- def showbrowser(self):
- """
- Show this response in a browser window (for debugging purposes,
- when it's hard to read the HTML).
- """
- import webbrowser
- fn = tempnam_no_warning(None, 'paste-fixture') + '.html'
- f = open(fn, 'wb')
- f.write(self.body)
- f.close()
- url = 'file:' + fn.replace(os.sep, '/')
- webbrowser.open_new(url)
-
-class TestRequest(object):
-
- # for py.test
- disabled = True
-
- """
- Instances of this class are created by `TestApp
- <class-paste.fixture.TestApp.html>`_ with the ``.get()`` and
- ``.post()`` methods, and are consumed there by ``.do_request()``.
-
- Instances are also available as a ``.req`` attribute on
- `TestResponse <class-paste.fixture.TestResponse.html>`_ instances.
-
- Useful attributes:
-
- ``url``:
- The url (actually usually the path) of the request, without
- query string.
-
- ``environ``:
- The environment dictionary used for the request.
-
- ``full_url``:
- The url/path, with query string.
- """
-
- def __init__(self, url, environ, expect_errors=False):
- if url.startswith('http://localhost'):
- url = url[len('http://localhost'):]
- self.url = url
- self.environ = environ
- if environ.get('QUERY_STRING'):
- self.full_url = url + '?' + environ['QUERY_STRING']
- else:
- self.full_url = url
- self.expect_errors = expect_errors
-
-
-class Form(object):
-
- """
- This object represents a form that has been found in a page.
- This has a couple useful attributes:
-
- ``text``:
- the full HTML of the form.
-
- ``action``:
- the relative URI of the action.
-
- ``method``:
- the method (e.g., ``'GET'``).
-
- ``id``:
- the id, or None if not given.
-
- ``fields``:
- a dictionary of fields, each value is a list of fields by
- that name. ``<input type=\"radio\">`` and ``<select>`` are
- both represented as single fields with multiple options.
- """
-
- # @@: This really should be using Mechanize/ClientForm or
- # something...
-
- _tag_re = re.compile(r'<(/?)([:a-z0-9_\-]*)([^>]*?)>', re.I)
-
- def __init__(self, response, text):
- self.response = response
- self.text = text
- self._parse_fields()
- self._parse_action()
-
- def _parse_fields(self):
- in_select = None
- in_textarea = None
- fields = {}
- for match in self._tag_re.finditer(self.text):
- end = match.group(1) == '/'
- tag = match.group(2).lower()
- if tag not in ('input', 'select', 'option', 'textarea',
- 'button'):
- continue
- if tag == 'select' and end:
- assert in_select, (
- '%r without starting select' % match.group(0))
- in_select = None
- continue
- if tag == 'textarea' and end:
- assert in_textarea, (
- "</textarea> with no <textarea> at %s" % match.start())
- in_textarea[0].value = html_unquote(self.text[in_textarea[1]:match.start()])
- in_textarea = None
- continue
- if end:
- continue
- attrs = _parse_attrs(match.group(3))
- if 'name' in attrs:
- name = attrs.pop('name')
- else:
- name = None
- if tag == 'option':
- in_select.options.append((attrs.get('value'),
- 'selected' in attrs))
- continue
- if tag == 'input' and attrs.get('type') == 'radio':
- field = fields.get(name)
- if not field:
- field = Radio(self, tag, name, match.start(), **attrs)
- fields.setdefault(name, []).append(field)
- else:
- field = field[0]
- assert isinstance(field, Radio)
- field.options.append((attrs.get('value'),
- 'checked' in attrs))
- continue
- tag_type = tag
- if tag == 'input':
- tag_type = attrs.get('type', 'text').lower()
- FieldClass = Field.classes.get(tag_type, Field)
- field = FieldClass(self, tag, name, match.start(), **attrs)
- if tag == 'textarea':
- assert not in_textarea, (
- "Nested textareas: %r and %r"
- % (in_textarea, match.group(0)))
- in_textarea = field, match.end()
- elif tag == 'select':
- assert not in_select, (
- "Nested selects: %r and %r"
- % (in_select, match.group(0)))
- in_select = field
- fields.setdefault(name, []).append(field)
- self.fields = fields
-
- def _parse_action(self):
- self.action = None
- for match in self._tag_re.finditer(self.text):
- end = match.group(1) == '/'
- tag = match.group(2).lower()
- if tag != 'form':
- continue
- if end:
- break
- attrs = _parse_attrs(match.group(3))
- self.action = attrs.get('action', '')
- self.method = attrs.get('method', 'GET')
- self.id = attrs.get('id')
- # @@: enctype?
- else:
- assert 0, "No </form> tag found"
- assert self.action is not None, (
- "No <form> tag found")
-
- def __setitem__(self, name, value):
- """
- Set the value of the named field. If there is 0 or multiple
- fields by that name, it is an error.
-
- Setting the value of a ``<select>`` selects the given option
- (and confirms it is an option). Setting radio fields does the
- same. Checkboxes get boolean values. You cannot set hidden
- fields or buttons.
-
- Use ``.set()`` if there is any ambiguity and you must provide
- an index.
- """
- fields = self.fields.get(name)
- assert fields is not None, (
- "No field by the name %r found (fields: %s)"
- % (name, ', '.join(map(repr, self.fields.keys()))))
- assert len(fields) == 1, (
- "Multiple fields match %r: %s"
- % (name, ', '.join(map(repr, fields))))
- fields[0].value = value
-
- def __getitem__(self, name):
- """
- Get the named field object (ambiguity is an error).
- """
- fields = self.fields.get(name)
- assert fields is not None, (
- "No field by the name %r found" % name)
- assert len(fields) == 1, (
- "Multiple fields match %r: %s"
- % (name, ', '.join(map(repr, fields))))
- return fields[0]
-
- def set(self, name, value, index=None):
- """
- Set the given name, using ``index`` to disambiguate.
- """
- if index is None:
- self[name] = value
- else:
- fields = self.fields.get(name)
- assert fields is not None, (
- "No fields found matching %r" % name)
- field = fields[index]
- field.value = value
-
- def get(self, name, index=None, default=NoDefault):
- """
- Get the named/indexed field object, or ``default`` if no field
- is found.
- """
- fields = self.fields.get(name)
- if fields is None and default is not NoDefault:
- return default
- if index is None:
- return self[name]
- else:
- fields = self.fields.get(name)
- assert fields is not None, (
- "No fields found matching %r" % name)
- field = fields[index]
- return field
-
- def select(self, name, value, index=None):
- """
- Like ``.set()``, except also confirms the target is a
- ``<select>``.
- """
- field = self.get(name, index=index)
- assert isinstance(field, Select)
- field.value = value
-
- def submit(self, name=None, index=None, **args):
- """
- Submits the form. If ``name`` is given, then also select that
- button (using ``index`` to disambiguate)``.
-
- Any extra keyword arguments are passed to the ``.get()`` or
- ``.post()`` method.
- """
- fields = self.submit_fields(name, index=index)
- return self.response.goto(self.action, method=self.method,
- params=fields, **args)
-
- def submit_fields(self, name=None, index=None):
- """
- Return a list of ``[(name, value), ...]`` for the current
- state of the form.
- """
- submit = []
- if name is not None:
- field = self.get(name, index=index)
- submit.append((field.name, field.value_if_submitted()))
- for name, fields in self.fields.items():
- if name is None:
- continue
- for field in fields:
- value = field.value
- if value is None:
- continue
- submit.append((name, value))
- return submit
-
-
-_attr_re = re.compile(r'([^= \n\r\t]+)[ \n\r\t]*(?:=[ \n\r\t]*(?:"([^"]*)"|([^"][^ \n\r\t>]*)))?', re.S)
-
-def _parse_attrs(text):
- attrs = {}
- for match in _attr_re.finditer(text):
- attr_name = match.group(1).lower()
- attr_body = match.group(2) or match.group(3)
- attr_body = html_unquote(attr_body or '')
- attrs[attr_name] = attr_body
- return attrs
-
-class Field(object):
-
- """
- Field object.
- """
-
- # Dictionary of field types (select, radio, etc) to classes
- classes = {}
-
- settable = True
-
- def __init__(self, form, tag, name, pos,
- value=None, id=None, **attrs):
- self.form = form
- self.tag = tag
- self.name = name
- self.pos = pos
- self._value = value
- self.id = id
- self.attrs = attrs
-
- def value__set(self, value):
- if not self.settable:
- raise AttributeError(
- "You cannot set the value of the <%s> field %r"
- % (self.tag, self.name))
- self._value = value
-
- def force_value(self, value):
- """
- Like setting a value, except forces it even for, say, hidden
- fields.
- """
- self._value = value
-
- def value__get(self):
- return self._value
-
- value = property(value__get, value__set)
-
-class Select(Field):
-
- """
- Field representing ``<select>``
- """
-
- def __init__(self, *args, **attrs):
- super(Select, self).__init__(*args, **attrs)
- self.options = []
- self.multiple = attrs.get('multiple')
- assert not self.multiple, (
- "<select multiple> not yet supported")
- # Undetermined yet:
- self.selectedIndex = None
-
- def value__set(self, value):
- for i, (option, checked) in enumerate(self.options):
- if option == str(value):
- self.selectedIndex = i
- break
- else:
- raise ValueError(
- "Option %r not found (from %s)"
- % (value, ', '.join(
- [repr(o) for o, c in self.options])))
-
- def value__get(self):
- if self.selectedIndex is not None:
- return self.options[self.selectedIndex][0]
- else:
- for option, checked in self.options:
- if checked:
- return option
- else:
- if self.options:
- return self.options[0][0]
- else:
- return None
-
- value = property(value__get, value__set)
-
-Field.classes['select'] = Select
-
-class Radio(Select):
-
- """
- Field representing ``<input type="radio">``
- """
-
-Field.classes['radio'] = Radio
-
-class Checkbox(Field):
-
- """
- Field representing ``<input type="checkbox">``
- """
-
- def __init__(self, *args, **attrs):
- super(Checkbox, self).__init__(*args, **attrs)
- self.checked = 'checked' in attrs
-
- def value__set(self, value):
- self.checked = not not value
-
- def value__get(self):
- if self.checked:
- if self._value is None:
- return 'on'
- else:
- return self._value
- else:
- return None
-
- value = property(value__get, value__set)
-
-Field.classes['checkbox'] = Checkbox
-
-class Text(Field):
- """
- Field representing ``<input type="text">``
- """
- def __init__(self, form, tag, name, pos,
- value='', id=None, **attrs):
- #text fields default to empty string
- Field.__init__(self, form, tag, name, pos,
- value=value, id=id, **attrs)
-
-Field.classes['text'] = Text
-
-class Textarea(Text):
- """
- Field representing ``<textarea>``
- """
-
-Field.classes['textarea'] = Textarea
-
-class Hidden(Text):
- """
- Field representing ``<input type="hidden">``
- """
-
-Field.classes['hidden'] = Hidden
-
-class Submit(Field):
- """
- Field representing ``<input type="submit">`` and ``<button>``
- """
-
- settable = False
-
- def value__get(self):
- return None
-
- value = property(value__get)
-
- def value_if_submitted(self):
- return self._value
-
-Field.classes['submit'] = Submit
-
-Field.classes['button'] = Submit
-
-Field.classes['image'] = Submit
-
-############################################################
-## Command-line testing
-############################################################
-
-
-class TestFileEnvironment(object):
-
- """
- This represents an environment in which files will be written, and
- scripts will be run.
- """
-
- # for py.test
- disabled = True
-
- def __init__(self, base_path, template_path=None,
- script_path=None,
- environ=None, cwd=None, start_clear=True,
- ignore_paths=None, ignore_hidden=True):
- """
- Creates an environment. ``base_path`` is used as the current
- working directory, and generally where changes are looked for.
-
- ``template_path`` is the directory to look for *template*
- files, which are files you'll explicitly add to the
- environment. This is done with ``.writefile()``.
-
- ``script_path`` is the PATH for finding executables. Usually
- grabbed from ``$PATH``.
-
- ``environ`` is the operating system environment,
- ``os.environ`` if not given.
-
- ``cwd`` is the working directory, ``base_path`` by default.
-
- If ``start_clear`` is true (default) then the ``base_path``
- will be cleared (all files deleted) when an instance is
- created. You can also use ``.clear()`` to clear the files.
-
- ``ignore_paths`` is a set of specific filenames that should be
- ignored when created in the environment. ``ignore_hidden``
- means, if true (default) that filenames and directories
- starting with ``'.'`` will be ignored.
- """
- self.base_path = base_path
- self.template_path = template_path
- if environ is None:
- environ = os.environ.copy()
- self.environ = environ
- if script_path is None:
- if sys.platform == 'win32':
- script_path = environ.get('PATH', '').split(';')
- else:
- script_path = environ.get('PATH', '').split(':')
- self.script_path = script_path
- if cwd is None:
- cwd = base_path
- self.cwd = cwd
- if start_clear:
- self.clear()
- elif not os.path.exists(base_path):
- os.makedirs(base_path)
- self.ignore_paths = ignore_paths or []
- self.ignore_hidden = ignore_hidden
-
- def run(self, script, *args, **kw):
- """
- Run the command, with the given arguments. The ``script``
- argument can have space-separated arguments, or you can use
- the positional arguments.
-
- Keywords allowed are:
-
- ``expect_error``: (default False)
- Don't raise an exception in case of errors
- ``expect_stderr``: (default ``expect_error``)
- Don't raise an exception if anything is printed to stderr
- ``stdin``: (default ``""``)
- Input to the script
- ``printresult``: (default True)
- Print the result after running
- ``cwd``: (default ``self.cwd``)
- The working directory to run in
-
- Returns a `ProcResponse
- <class-paste.fixture.ProcResponse.html>`_ object.
- """
- __tracebackhide__ = True
- expect_error = _popget(kw, 'expect_error', False)
- expect_stderr = _popget(kw, 'expect_stderr', expect_error)
- cwd = _popget(kw, 'cwd', self.cwd)
- stdin = _popget(kw, 'stdin', None)
- printresult = _popget(kw, 'printresult', True)
- args = map(str, args)
- assert not kw, (
- "Arguments not expected: %s" % ', '.join(kw.keys()))
- if ' ' in script:
- assert not args, (
- "You cannot give a multi-argument script (%r) "
- "and arguments (%s)" % (script, args))
- script, args = script.split(None, 1)
- args = shlex.split(args)
- script = self._find_exe(script)
- all = [script] + args
- files_before = self._find_files()
- proc = subprocess.Popen(all, stdin=subprocess.PIPE,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- cwd=cwd,
- env=self.environ)
- stdout, stderr = proc.communicate(stdin)
- files_after = self._find_files()
- result = ProcResult(
- self, all, stdin, stdout, stderr,
- returncode=proc.returncode,
- files_before=files_before,
- files_after=files_after)
- if printresult:
- print result
- print '-'*40
- if not expect_error:
- result.assert_no_error()
- if not expect_stderr:
- result.assert_no_stderr()
- return result
-
- def _find_exe(self, script_name):
- if self.script_path is None:
- script_name = os.path.join(self.cwd, script_name)
- if not os.path.exists(script_name):
- raise OSError(
- "Script %s does not exist" % script_name)
- return script_name
- for path in self.script_path:
- fn = os.path.join(path, script_name)
- if os.path.exists(fn):
- return fn
- raise OSError(
- "Script %s could not be found in %s"
- % (script_name, ':'.join(self.script_path)))
-
- def _find_files(self):
- result = {}
- for fn in os.listdir(self.base_path):
- if self._ignore_file(fn):
- continue
- self._find_traverse(fn, result)
- return result
-
- 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):
- result[path] = FoundDir(self.base_path, path)
- for fn in os.listdir(full):
- fn = os.path.join(path, fn)
- if self._ignore_file(fn):
- continue
- self._find_traverse(fn, result)
- else:
- result[path] = FoundFile(self.base_path, path)
-
- def clear(self):
- """
- Delete all the files in the base directory.
- """
- if os.path.exists(self.base_path):
- shutil.rmtree(self.base_path)
- os.mkdir(self.base_path)
-
- def writefile(self, path, content=None,
- frompath=None):
- """
- Write a file to the given path. If ``content`` is given then
- that text is written, otherwise the file in ``frompath`` is
- used. ``frompath`` is relative to ``self.template_path``
- """
- full = os.path.join(self.base_path, path)
- if not os.path.exists(os.path.dirname(full)):
- os.makedirs(os.path.dirname(full))
- f = open(full, 'wb')
- if content is not None:
- f.write(content)
- if frompath is not None:
- if self.template_path:
- frompath = os.path.join(self.template_path, frompath)
- f2 = open(frompath, 'rb')
- f.write(f2.read())
- f2.close()
- f.close()
- return FoundFile(self.base_path, path)
-
-class ProcResult(object):
-
- """
- Represents the results of running a command in
- `TestFileEnvironment
- <class-paste.fixture.TestFileEnvironment.html>`_.
-
- Attributes to pay particular attention to:
-
- ``stdout``, ``stderr``:
- What is produced
-
- ``files_created``, ``files_deleted``, ``files_updated``:
- Dictionaries mapping filenames (relative to the ``base_dir``)
- to `FoundFile <class-paste.fixture.FoundFile.html>`_ or
- `FoundDir <class-paste.fixture.FoundDir.html>`_ objects.
- """
-
- def __init__(self, test_env, args, stdin, stdout, stderr,
- returncode, files_before, files_after):
- self.test_env = test_env
- self.args = args
- self.stdin = stdin
- self.stdout = stdout
- self.stderr = stderr
- self.returncode = returncode
- self.files_before = files_before
- self.files_after = files_after
- self.files_deleted = {}
- self.files_updated = {}
- self.files_created = files_after.copy()
- for path, f in files_before.items():
- if path not in files_after:
- self.files_deleted[path] = f
- continue
- del self.files_created[path]
- if f.mtime < files_after[path].mtime:
- self.files_updated[path] = files_after[path]
-
- def assert_no_error(self):
- __tracebackhide__ = True
- assert self.returncode == 0, (
- "Script returned code: %s" % self.returncode)
-
- def assert_no_stderr(self):
- __tracebackhide__ = True
- if self.stderr:
- print 'Error output:'
- print self.stderr
- raise AssertionError("stderr output not expected")
-
- def __str__(self):
- s = ['Script result: %s' % ' '.join(self.args)]
- if self.returncode:
- s.append(' return code: %s' % self.returncode)
- if self.stderr:
- s.append('-- stderr: --------------------')
- s.append(self.stderr)
- if self.stdout:
- s.append('-- stdout: --------------------')
- s.append(self.stdout)
- for name, files, show_size in [
- ('created', self.files_created, True),
- ('deleted', self.files_deleted, True),
- ('updated', self.files_updated, True)]:
- if files:
- s.append('-- %s: -------------------' % name)
- files = files.items()
- files.sort()
- last = ''
- for path, f in files:
- t = ' %s' % _space_prefix(last, path, indent=4,
- include_sep=False)
- last = path
- if show_size and f.size != 'N/A':
- t += ' (%s bytes)' % f.size
- s.append(t)
- return '\n'.join(s)
-
-class FoundFile(object):
-
- """
- Represents a single file found as the result of a command.
-
- Has attributes:
-
- ``path``:
- The path of the file, relative to the ``base_path``
-
- ``full``:
- The full path
-
- ``stat``:
- The results of ``os.stat``. Also ``mtime`` and ``size``
- contain the ``.st_mtime`` and ``st_size`` of the stat.
-
- ``bytes``:
- The contents of the file.
-
- You may use the ``in`` operator with these objects (tested against
- the contents of the file), and the ``.mustcontain()`` method.
- """
-
- file = True
- dir = False
-
- def __init__(self, base_path, path):
- self.base_path = base_path
- self.path = path
- self.full = os.path.join(base_path, path)
- self.stat = os.stat(self.full)
- self.mtime = self.stat.st_mtime
- self.size = self.stat.st_size
- self._bytes = None
-
- def bytes__get(self):
- if self._bytes is None:
- f = open(self.full, 'rb')
- self._bytes = f.read()
- f.close()
- return self._bytes
- bytes = property(bytes__get)
-
- def __contains__(self, s):
- return s in self.bytes
-
- def mustcontain(self, s):
- __tracebackhide__ = True
- bytes = self.bytes
- if s not in bytes:
- print 'Could not find %r in:' % s
- print bytes
- assert s in bytes
-
- def __repr__(self):
- return '<%s %s:%s>' % (
- self.__class__.__name__,
- self.base_path, self.path)
-
-class FoundDir(object):
-
- """
- Represents a directory created by a command.
- """
-
- file = False
- dir = True
-
- def __init__(self, base_path, path):
- self.base_path = base_path
- self.path = path
- self.full = os.path.join(base_path, path)
- self.size = 'N/A'
- self.mtime = 'N/A'
-
- def __repr__(self):
- return '<%s %s:%s>' % (
- self.__class__.__name__,
- self.base_path, self.path)
-
-def _popget(d, key, default=None):
- """
- Pop the key if found (else return default)
- """
- if key in d:
- return d.pop(key)
- return default
-
-def _space_prefix(pref, full, sep=None, indent=None, include_sep=True):
- """
- Anything shared by pref and full will be replaced with spaces
- in full, and full returned.
- """
- if sep is None:
- sep = os.path.sep
- pref = pref.split(sep)
- full = full.split(sep)
- padding = []
- while pref and full and pref[0] == full[0]:
- if indent is None:
- padding.append(' ' * (len(full[0]) + len(sep)))
- else:
- padding.append(' ' * indent)
- full.pop(0)
- pref.pop(0)
- if padding:
- if include_sep:
- return ''.join(padding) + sep + sep.join(full)
- else:
- return ''.join(padding) + sep.join(full)
- else:
- return sep.join(full)
-
-def _make_pattern(pat):
- if pat is None:
- return None
- if isinstance(pat, (str, unicode)):
- pat = re.compile(pat)
- if hasattr(pat, 'search'):
- return pat.search
- if callable(pat):
- return pat
- assert 0, (
- "Cannot make callable pattern object out of %r" % pat)
-
-def setup_module(module=None):
- """
- This is used by py.test if it is in the module, so you can
- import this directly.
-
- Use like::
-
- from paste.fixture import setup_module
- """
- # Deprecated June 2008
- import warnings
- warnings.warn(
- 'setup_module is deprecated',
- DeprecationWarning, 2)
- if module is None:
- # The module we were called from must be the module...
- module = sys._getframe().f_back.f_globals['__name__']
- if isinstance(module, (str, unicode)):
- module = sys.modules[module]
- if hasattr(module, 'reset_state'):
- module.reset_state()
-
-def html_unquote(v):
- """
- Unquote (some) entities in HTML. (incomplete)
- """
- for ent, repl in [(' ', ' '), ('>', '>'),
- ('<', '<'), ('"', '"'),
- ('&', '&')]:
- v = v.replace(ent, repl)
- return v
diff --git a/lib/paste/flup_session.py b/lib/paste/flup_session.py
@@ -1,108 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-Creates a session object.
-
-In your application, use::
-
- environ['paste.flup_session_service'].session
-
-This will return a dictionary. The contents of this dictionary will
-be saved to disk when the request is completed. The session will be
-created when you first fetch the session dictionary, and a cookie will
-be sent in that case. There's current no way to use sessions without
-cookies, and there's no way to delete a session except to clear its
-data.
-"""
-
-from paste import httpexceptions
-from paste import wsgilib
-import flup.middleware.session
-flup_session = flup.middleware.session
-
-# This is a dictionary of existing stores, keyed by a tuple of
-# store type and parameters
-store_cache = {}
-
-class NoDefault(object):
- pass
-
-class SessionMiddleware(object):
-
- session_classes = {
- 'memory': (flup_session.MemorySessionStore,
- [('session_timeout', 'timeout', int, 60)]),
- 'disk': (flup_session.DiskSessionStore,
- [('session_timeout', 'timeout', int, 60),
- ('session_dir', 'storeDir', str, '/tmp/sessions')]),
- 'shelve': (flup_session.ShelveSessionStore,
- [('session_timeout', 'timeout', int, 60),
- ('session_file', 'storeFile', str,
- '/tmp/session.shelve')]),
- }
-
-
- def __init__(self, app,
- global_conf=None,
- session_type=NoDefault,
- cookie_name=NoDefault,
- **store_config
- ):
- self.application = app
- if session_type is NoDefault:
- session_type = global_conf.get('session_type', 'disk')
- self.session_type = session_type
- try:
- self.store_class, self.store_args = self.session_classes[self.session_type]
- except KeyError:
- raise KeyError(
- "The session_type %s is unknown (I know about %s)"
- % (self.session_type,
- ', '.join(self.session_classes.keys())))
- kw = {}
- for config_name, kw_name, coercer, default in self.store_args:
- value = coercer(store_config.get(config_name, default))
- kw[kw_name] = value
- self.store = self.store_class(**kw)
- 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,
- fieldName=self.cookie_name)
- environ['paste.flup_session_service'] = service
-
- def cookie_start_response(status, headers, exc_info=None):
- service.addCookie(headers)
- return start_response(status, headers, exc_info)
-
- try:
- app_iter = self.application(environ, cookie_start_response)
- except httpexceptions.HTTPException, e:
- headers = (e.headers or {}).items()
- service.addCookie(headers)
- e.headers = dict(headers)
- service.close()
- raise
- except:
- service.close()
- raise
-
- return wsgilib.add_close(app_iter, service.close)
-
-def make_session_middleware(app, global_conf,
- session_type=NoDefault,
- cookie_name=NoDefault,
- **store_config):
- """
- Wraps the application in a session-managing middleware.
- The session service can then be found in
- ``environ['paste.flup_session_service']``
- """
- return SessionMiddleware(
- app, global_conf=global_conf,
- session_type=session_type, cookie_name=cookie_name,
- **store_config)
diff --git a/lib/paste/gzipper.py b/lib/paste/gzipper.py
@@ -1,111 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-WSGI middleware
-
-Gzip-encodes the response.
-"""
-
-import gzip
-from paste.response import header_value, remove_header
-from paste.httpheaders import CONTENT_LENGTH
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-class GzipOutput(object):
- pass
-
-class middleware(object):
-
- def __init__(self, application, compress_level=6):
- self.application = application
- self.compress_level = int(compress_level)
-
- def __call__(self, environ, start_response):
- if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''):
- # nothing for us to do, so this middleware will
- # be a no-op:
- return self.application(environ, start_response)
- response = GzipResponse(start_response, self.compress_level)
- app_iter = self.application(environ,
- response.gzip_start_response)
- if app_iter:
- response.finish_response(app_iter)
-
- return response.write()
-
-class GzipResponse(object):
-
- def __init__(self, start_response, compress_level):
- self.start_response = start_response
- self.compress_level = compress_level
- self.buffer = StringIO()
- self.compressible = False
- self.content_length = None
-
- def gzip_start_response(self, status, headers, exc_info=None):
- self.headers = headers
- ct = header_value(headers,'content-type')
- ce = header_value(headers,'content-encoding')
- self.compressible = False
- if ct and (ct.startswith('text/') or ct.startswith('application/')) \
- and 'zip' not in ct:
- self.compressible = True
- if ce:
- self.compressible = False
- if self.compressible:
- headers.append(('content-encoding', 'gzip'))
- remove_header(headers, 'content-length')
- self.headers = headers
- self.status = status
- return self.buffer.write
-
- def write(self):
- out = self.buffer
- out.seek(0)
- s = out.getvalue()
- out.close()
- return [s]
-
- def finish_response(self, app_iter):
- if self.compressible:
- output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level,
- fileobj=self.buffer)
- else:
- output = self.buffer
- try:
- for s in app_iter:
- output.write(s)
- if self.compressible:
- output.close()
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- content_length = self.buffer.tell()
- CONTENT_LENGTH.update(self.headers, content_length)
- self.start_response(self.status, self.headers)
-
-def filter_factory(application, **conf):
- import warnings
- warnings.warn(
- 'This function is deprecated; use make_gzip_middleware instead',
- DeprecationWarning, 2)
- def filter(application):
- return middleware(application)
- return filter
-
-def make_gzip_middleware(app, global_conf, compress_level=6):
- """
- Wrap the middleware, so that it applies gzipping to a response
- when it is supported by the browser and the content is of
- type ``text/*`` or ``application/*``
- """
- compress_level = int(compress_level)
- return middleware(app, compress_level=compress_level)
diff --git a/lib/paste/httpexceptions.py b/lib/paste/httpexceptions.py
@@ -1,664 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# (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
-# Some of this code was funded by http://prometheusresearch.com
-"""
-HTTP Exception Middleware
-
-This module processes Python exceptions that relate to HTTP exceptions
-by defining a set of exceptions, all subclasses of HTTPException, and a
-request handler (`middleware`) that catches these exceptions and turns
-them into proper responses.
-
-This module defines exceptions according to RFC 2068 [1]_ : codes with
-100-300 are not really errors; 400's are client errors, and 500's are
-server errors. According to the WSGI specification [2]_ , the application
-can call ``start_response`` more then once only under two conditions:
-(a) the response has not yet been sent, or (b) if the second and
-subsequent invocations of ``start_response`` have a valid ``exc_info``
-argument obtained from ``sys.exc_info()``. The WSGI specification then
-requires the server or gateway to handle the case where content has been
-sent and then an exception was encountered.
-
-Exceptions in the 5xx range and those raised after ``start_response``
-has been called are treated as serious errors and the ``exc_info`` is
-filled-in with information needed for a lower level module to generate a
-stack trace and log information.
-
-Exception
- HTTPException
- HTTPRedirection
- * 300 - HTTPMultipleChoices
- * 301 - HTTPMovedPermanently
- * 302 - HTTPFound
- * 303 - HTTPSeeOther
- * 304 - HTTPNotModified
- * 305 - HTTPUseProxy
- * 306 - Unused (not implemented, obviously)
- * 307 - HTTPTemporaryRedirect
- HTTPError
- HTTPClientError
- * 400 - HTTPBadRequest
- * 401 - HTTPUnauthorized
- * 402 - HTTPPaymentRequired
- * 403 - HTTPForbidden
- * 404 - HTTPNotFound
- * 405 - HTTPMethodNotAllowed
- * 406 - HTTPNotAcceptable
- * 407 - HTTPProxyAuthenticationRequired
- * 408 - HTTPRequestTimeout
- * 409 - HTTPConfict
- * 410 - HTTPGone
- * 411 - HTTPLengthRequired
- * 412 - HTTPPreconditionFailed
- * 413 - HTTPRequestEntityTooLarge
- * 414 - HTTPRequestURITooLong
- * 415 - HTTPUnsupportedMediaType
- * 416 - HTTPRequestRangeNotSatisfiable
- * 417 - HTTPExpectationFailed
- HTTPServerError
- * 500 - HTTPInternalServerError
- * 501 - HTTPNotImplemented
- * 502 - HTTPBadGateway
- * 503 - HTTPServiceUnavailable
- * 504 - HTTPGatewayTimeout
- * 505 - HTTPVersionNotSupported
-
-References:
-
-.. [1] http://www.python.org/peps/pep-0333.html#error-handling
-.. [2] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5
-
-"""
-
-import types
-from paste.wsgilib import catch_errors_app
-from paste.response import has_header, header_value, replace_header
-from paste.request import resolve_relative_url
-from paste.util.quoting import strip_html, html_quote, no_quote
-
-SERVER_NAME = 'WSGI Server'
-TEMPLATE = """\
-<html>\r
- <head><title>%(title)s</title></head>\r
- <body>\r
- <h1>%(title)s</h1>\r
- <p>%(body)s</p>\r
- <hr noshade>\r
- <div align="right">%(server)s</div>\r
- </body>\r
-</html>\r
-"""
-
-class HTTPException(Exception):
- """
- the HTTP exception base class
-
- This encapsulates an HTTP response that interrupts normal application
- flow; but one which is not necessarly an error condition. For
- example, codes in the 300's are exceptions in that they interrupt
- normal processing; however, they are not considered errors.
-
- This class is complicated by 4 factors:
-
- 1. The content given to the exception may either be plain-text or
- as html-text.
-
- 2. The template may want to have string-substitutions taken from
- the current ``environ`` or values from incoming headers. This
- is especially troublesome due to case sensitivity.
-
- 3. The final output may either be text/plain or text/html
- mime-type as requested by the client application.
-
- 4. Each exception has a default explanation, but those who
- raise exceptions may want to provide additional detail.
-
- Attributes:
-
- ``code``
- the HTTP status code for the exception
-
- ``title``
- remainder of the status line (stuff after the code)
-
- ``explanation``
- a plain-text explanation of the error message that is
- not subject to environment or header substitutions;
- it is accessible in the template via %(explanation)s
-
- ``detail``
- a plain-text message customization that is not subject
- to environment or header substitutions; accessible in
- the template via %(detail)s
-
- ``template``
- a content fragment (in HTML) used for environment and
- header substitution; the default template includes both
- the explanation and further detail provided in the
- message
-
- ``required_headers``
- a sequence of headers which are required for proper
- construction of the exception
-
- Parameters:
-
- ``detail``
- a plain-text override of the default ``detail``
-
- ``headers``
- a list of (k,v) header pairs
-
- ``comment``
- a plain-text additional information which is
- usually stripped/hidden for end-users
-
- To override the template (which is HTML content) or the plain-text
- explanation, one must subclass the given exception; or customize it
- after it has been created. This particular breakdown of a message
- into explanation, detail and template allows both the creation of
- plain-text and html messages for various clients as well as
- error-free substitution of environment variables and headers.
- """
-
- code = None
- title = None
- explanation = ''
- detail = ''
- comment = ''
- template = "%(explanation)s\r\n<br/>%(detail)s\r\n<!-- %(comment)s -->"
- required_headers = ()
-
- def __init__(self, detail=None, headers=None, comment=None):
- assert self.code, "Do not directly instantiate abstract exceptions."
- assert isinstance(headers, (type(None), list)), (
- "headers must be None or a list: %r"
- % headers)
- assert isinstance(detail, (type(None), basestring)), (
- "detail must be None or a string: %r" % detail)
- assert isinstance(comment, (type(None), basestring)), (
- "comment must be None or a string: %r" % comment)
- self.headers = headers or tuple()
- for req in self.required_headers:
- assert headers and has_header(headers, req), (
- "Exception %s must be passed the header %r "
- "(got headers: %r)"
- % (self.__class__.__name__, req, headers))
- if detail is not None:
- self.detail = detail
- if comment is not None:
- self.comment = comment
- Exception.__init__(self,"%s %s\n%s\n%s\n" % (
- self.code, self.title, self.explanation, self.detail))
-
- def make_body(self, environ, template, escfunc, comment_escfunc=None):
- comment_escfunc = comment_escfunc or escfunc
- args = {'explanation': escfunc(self.explanation),
- 'detail': escfunc(self.detail),
- 'comment': comment_escfunc(self.comment)}
- if HTTPException.template == self.template:
- return template % args
- for (k, v) in environ.items():
- args[k] = escfunc(v)
- if self.headers:
- for (k, v) in self.headers:
- args[k.lower()] = escfunc(v)
- return template % args
-
- def plain(self, environ):
- """ text/plain representation of the exception """
- body = self.make_body(environ, strip_html(self.template), no_quote)
- return ('%s %s\r\n%s\r\n' % (self.code, self.title, body))
-
- def html(self, environ):
- """ text/html representation of the exception """
- body = self.make_body(environ, self.template, html_quote, no_quote)
- return TEMPLATE % {
- 'title': self.title,
- 'code': self.code,
- 'server': SERVER_NAME,
- 'body': body }
-
- def prepare_content(self, environ):
- if self.headers:
- headers = list(self.headers)
- else:
- headers = []
- if 'html' in environ.get('HTTP_ACCEPT','') or \
- '*/*' in environ.get('HTTP_ACCEPT',''):
- replace_header(headers, 'content-type', 'text/html')
- content = self.html(environ)
- else:
- replace_header(headers, 'content-type', 'text/plain')
- content = self.plain(environ)
- if isinstance(content, unicode):
- content = content.encode('utf8')
- cur_content_type = (
- header_value(headers, 'content-type')
- or 'text/html')
- replace_header(
- headers, 'content-type',
- cur_content_type + '; charset=utf8')
- return headers, content
-
- def response(self, environ):
- from paste.wsgiwrappers import WSGIResponse
- headers, content = self.prepare_content(environ)
- resp = WSGIResponse(code=self.code, content=content)
- resp.headers = resp.headers.fromlist(headers)
- return resp
-
- def wsgi_application(self, environ, start_response, exc_info=None):
- """
- This exception as a WSGI application
- """
- headers, content = self.prepare_content(environ)
- start_response('%s %s' % (self.code, self.title),
- headers,
- exc_info)
- return [content]
-
- __call__ = wsgi_application
-
- def __repr__(self):
- return '<%s %s; code=%s>' % (self.__class__.__name__,
- self.title, self.code)
-
-class HTTPError(HTTPException):
- """
- base class for status codes in the 400's and 500's
-
- This is an exception which indicates that an error has occurred,
- and that any work in progress should not be committed. These are
- typically results in the 400's and 500's.
- """
-
-#
-# 3xx Redirection
-#
-# This class of status code indicates that further action needs to be
-# taken by the user agent in order to fulfill the request. The action
-# required MAY be carried out by the user agent without interaction with
-# the user if and only if the method used in the second request is GET or
-# HEAD. A client SHOULD detect infinite redirection loops, since such
-# loops generate network traffic for each redirection.
-#
-
-class HTTPRedirection(HTTPException):
- """
- base class for 300's status code (redirections)
-
- This is an abstract base class for 3xx redirection. It indicates
- that further action needs to be taken by the user agent in order
- to fulfill the request. It does not necessarly signal an error
- condition.
- """
-
-class _HTTPMove(HTTPRedirection):
- """
- redirections which require a Location field
-
- Since a 'Location' header is a required attribute of 301, 302, 303,
- 305 and 307 (but not 304), this base class provides the mechanics to
- make this easy. While this has the same parameters as HTTPException,
- if a location is not provided in the headers; it is assumed that the
- detail _is_ the location (this for backward compatibility, otherwise
- we'd add a new attribute).
- """
- required_headers = ('location',)
- explanation = 'The resource has been moved to'
- template = (
- '%(explanation)s <a href="%(location)s">%(location)s</a>;\r\n'
- 'you should be redirected automatically.\r\n'
- '%(detail)s\r\n<!-- %(comment)s -->')
-
- def __init__(self, detail=None, headers=None, comment=None):
- assert isinstance(headers, (type(None), list))
- headers = headers or []
- location = header_value(headers,'location')
- if not location:
- location = detail
- detail = ''
- headers.append(('location', location))
- assert location, ("HTTPRedirection specified neither a "
- "location in the headers nor did it "
- "provide a detail argument.")
- HTTPRedirection.__init__(self, location, headers, comment)
- if detail is not None:
- self.detail = detail
-
- def relative_redirect(cls, dest_uri, environ, detail=None, headers=None, comment=None):
- """
- Create a redirect object with the dest_uri, which may be relative,
- considering it relative to the uri implied by the given environ.
- """
- location = resolve_relative_url(dest_uri, environ)
- headers = headers or []
- headers.append(('Location', location))
- return cls(detail=detail, headers=headers, comment=comment)
-
- relative_redirect = classmethod(relative_redirect)
-
- def location(self):
- for name, value in self.headers:
- if name.lower() == 'location':
- return value
- else:
- raise KeyError("No location set for %s" % self)
-
-class HTTPMultipleChoices(_HTTPMove):
- code = 300
- title = 'Multiple Choices'
-
-class HTTPMovedPermanently(_HTTPMove):
- code = 301
- title = 'Moved Permanently'
-
-class HTTPFound(_HTTPMove):
- code = 302
- title = 'Found'
- explanation = 'The resource was found at'
-
-# This one is safe after a POST (the redirected location will be
-# retrieved with GET):
-class HTTPSeeOther(_HTTPMove):
- code = 303
- title = 'See Other'
-
-class HTTPNotModified(HTTPRedirection):
- # @@: but not always (HTTP section 14.18.1)...?
- # @@: Removed 'date' requirement, as its not required for an ETag
- # @@: FIXME: This should require either an ETag or a date header
- code = 304
- title = 'Not Modified'
- message = ''
- # @@: should include date header, optionally other headers
- # @@: should not return a content body
- def plain(self, environ):
- return ''
- def html(self, environ):
- """ text/html representation of the exception """
- return ''
-
-class HTTPUseProxy(_HTTPMove):
- # @@: OK, not a move, but looks a little like one
- code = 305
- title = 'Use Proxy'
- explanation = (
- 'The resource must be accessed through a proxy '
- 'located at')
-
-class HTTPTemporaryRedirect(_HTTPMove):
- code = 307
- title = 'Temporary Redirect'
-
-#
-# 4xx Client Error
-#
-# The 4xx class of status code is intended for cases in which the client
-# seems to have erred. Except when responding to a HEAD request, the
-# server SHOULD include an entity containing an explanation of the error
-# situation, and whether it is a temporary or permanent condition. These
-# status codes are applicable to any request method. User agents SHOULD
-# display any included entity to the user.
-#
-
-class HTTPClientError(HTTPError):
- """
- base class for the 400's, where the client is in-error
-
- This is an error condition in which the client is presumed to be
- in-error. This is an expected problem, and thus is not considered
- a bug. A server-side traceback is not warranted. Unless specialized,
- this is a '400 Bad Request'
- """
- code = 400
- title = 'Bad Request'
- explanation = ('The server could not comply with the request since\r\n'
- 'it is either malformed or otherwise incorrect.\r\n')
-
-class HTTPBadRequest(HTTPClientError):
- pass
-
-class HTTPUnauthorized(HTTPClientError):
- code = 401
- title = 'Unauthorized'
- explanation = (
- 'This server could not verify that you are authorized to\r\n'
- 'access the document you requested. Either you supplied the\r\n'
- 'wrong credentials (e.g., bad password), or your browser\r\n'
- 'does not understand how to supply the credentials required.\r\n')
-
-class HTTPPaymentRequired(HTTPClientError):
- code = 402
- title = 'Payment Required'
- explanation = ('Access was denied for financial reasons.')
-
-class HTTPForbidden(HTTPClientError):
- code = 403
- title = 'Forbidden'
- explanation = ('Access was denied to this resource.')
-
-class HTTPNotFound(HTTPClientError):
- code = 404
- title = 'Not Found'
- explanation = ('The resource could not be found.')
-
-class HTTPMethodNotAllowed(HTTPClientError):
- required_headers = ('allow',)
- code = 405
- title = 'Method Not Allowed'
- # override template since we need an environment variable
- template = ('The method %(REQUEST_METHOD)s is not allowed for '
- 'this resource.\r\n%(detail)s')
-
-class HTTPNotAcceptable(HTTPClientError):
- code = 406
- title = 'Not Acceptable'
- # override template since we need an environment variable
- template = ('The resource could not be generated that was '
- 'acceptable to your browser (content\r\nof type '
- '%(HTTP_ACCEPT)s).\r\n%(detail)s')
-
-class HTTPProxyAuthenticationRequired(HTTPClientError):
- code = 407
- title = 'Proxy Authentication Required'
- explanation = ('Authentication /w a local proxy is needed.')
-
-class HTTPRequestTimeout(HTTPClientError):
- code = 408
- title = 'Request Timeout'
- explanation = ('The server has waited too long for the request to '
- 'be sent by the client.')
-
-class HTTPConflict(HTTPClientError):
- code = 409
- title = 'Conflict'
- explanation = ('There was a conflict when trying to complete '
- 'your request.')
-
-class HTTPGone(HTTPClientError):
- code = 410
- title = 'Gone'
- explanation = ('This resource is no longer available. No forwarding '
- 'address is given.')
-
-class HTTPLengthRequired(HTTPClientError):
- code = 411
- title = 'Length Required'
- explanation = ('Content-Length header required.')
-
-class HTTPPreconditionFailed(HTTPClientError):
- code = 412
- title = 'Precondition Failed'
- explanation = ('Request precondition failed.')
-
-class HTTPRequestEntityTooLarge(HTTPClientError):
- code = 413
- title = 'Request Entity Too Large'
- explanation = ('The body of your request was too large for this server.')
-
-class HTTPRequestURITooLong(HTTPClientError):
- code = 414
- title = 'Request-URI Too Long'
- explanation = ('The request URI was too long for this server.')
-
-class HTTPUnsupportedMediaType(HTTPClientError):
- code = 415
- title = 'Unsupported Media Type'
- # override template since we need an environment variable
- template = ('The request media type %(CONTENT_TYPE)s is not '
- 'supported by this server.\r\n%(detail)s')
-
-class HTTPRequestRangeNotSatisfiable(HTTPClientError):
- code = 416
- title = 'Request Range Not Satisfiable'
- explanation = ('The Range requested is not available.')
-
-class HTTPExpectationFailed(HTTPClientError):
- code = 417
- title = 'Expectation Failed'
- explanation = ('Expectation failed.')
-
-#
-# 5xx Server Error
-#
-# Response status codes beginning with the digit "5" indicate cases in
-# which the server is aware that it has erred or is incapable of
-# performing the request. Except when responding to a HEAD request, the
-# server SHOULD include an entity containing an explanation of the error
-# situation, and whether it is a temporary or permanent condition. User
-# agents SHOULD display any included entity to the user. These response
-# codes are applicable to any request method.
-#
-
-class HTTPServerError(HTTPError):
- """
- base class for the 500's, where the server is in-error
-
- This is an error condition in which the server is presumed to be
- in-error. This is usually unexpected, and thus requires a traceback;
- ideally, opening a support ticket for the customer. Unless specialized,
- this is a '500 Internal Server Error'
- """
- code = 500
- title = 'Internal Server Error'
- explanation = (
- 'The server has either erred or is incapable of performing\r\n'
- 'the requested operation.\r\n')
-
-class HTTPInternalServerError(HTTPServerError):
- pass
-
-class HTTPNotImplemented(HTTPServerError):
- code = 501
- title = 'Not Implemented'
- # override template since we need an environment variable
- template = ('The request method %(REQUEST_METHOD)s is not implemented '
- 'for this server.\r\n%(detail)s')
-
-class HTTPBadGateway(HTTPServerError):
- code = 502
- title = 'Bad Gateway'
- explanation = ('Bad gateway.')
-
-class HTTPServiceUnavailable(HTTPServerError):
- code = 503
- title = 'Service Unavailable'
- explanation = ('The server is currently unavailable. '
- 'Please try again at a later time.')
-
-class HTTPGatewayTimeout(HTTPServerError):
- code = 504
- title = 'Gateway Timeout'
- explanation = ('The gateway has timed out.')
-
-class HTTPVersionNotSupported(HTTPServerError):
- code = 505
- title = 'HTTP Version Not Supported'
- explanation = ('The HTTP version is not supported.')
-
-# abstract HTTP related exceptions
-__all__ = ['HTTPException', 'HTTPRedirection', 'HTTPError' ]
-
-_exceptions = {}
-for name, value in globals().items():
- if (isinstance(value, (type, types.ClassType)) and
- issubclass(value, HTTPException) and
- value.code):
- _exceptions[value.code] = value
- __all__.append(name)
-
-def get_exception(code):
- return _exceptions[code]
-
-############################################################
-## Middleware implementation:
-############################################################
-
-class HTTPExceptionHandler(object):
- """
- catches exceptions and turns them into proper HTTP responses
-
- Attributes:
-
- ``warning_level``
- This attribute determines for what exceptions a stack
- trace is kept for lower level reporting; by default, it
- only keeps stack trace for 5xx, HTTPServerError exceptions.
- To keep a stack trace for 4xx, HTTPClientError exceptions,
- set this to 400.
-
- This middleware catches any exceptions (which are subclasses of
- ``HTTPException``) and turns them into proper HTTP responses.
- Note if the headers have already been sent, the stack trace is
- always maintained as this indicates a programming error.
-
- Note that you must raise the exception before returning the
- app_iter, and you cannot use this with generator apps that don't
- raise an exception until after their app_iter is iterated over.
- """
-
- def __init__(self, application, warning_level=None):
- assert not warning_level or ( warning_level > 99 and
- warning_level < 600)
- self.warning_level = warning_level or 500
- self.application = application
-
- def __call__(self, environ, start_response):
- environ['paste.httpexceptions'] = self
- environ.setdefault('paste.expected_exceptions',
- []).append(HTTPException)
- try:
- return self.application(environ, start_response)
- except HTTPException, exc:
- return exc(environ, start_response)
-
-def middleware(*args, **kw):
- import warnings
- # deprecated 13 dec 2005
- warnings.warn('httpexceptions.middleware is deprecated; use '
- 'make_middleware or HTTPExceptionHandler instead',
- DeprecationWarning, 2)
- return make_middleware(*args, **kw)
-
-def make_middleware(app, global_conf=None, warning_level=None):
- """
- ``httpexceptions`` middleware; this catches any
- ``paste.httpexceptions.HTTPException`` exceptions (exceptions like
- ``HTTPNotFound``, ``HTTPMovedPermanently``, etc) and turns them
- into proper HTTP responses.
-
- ``warning_level`` can be an integer corresponding to an HTTP code.
- Any code over that value will be passed 'up' the chain, potentially
- reported on by another piece of middleware.
- """
- if warning_level:
- warning_level = int(warning_level)
- return HTTPExceptionHandler(app, warning_level=warning_level)
-
-__all__.extend(['HTTPExceptionHandler', 'get_exception'])
-
diff --git a/lib/paste/httpheaders.py b/lib/paste/httpheaders.py
@@ -1,1097 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# (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
-# Some of this code was funded by: http://prometheusresearch.com
-"""
-HTTP Message Header Fields (see RFC 4229)
-
-This contains general support for HTTP/1.1 message headers [1]_ in a
-manner that supports WSGI ``environ`` [2]_ and ``response_headers``
-[3]_. Specifically, this module defines a ``HTTPHeader`` class whose
-instances correspond to field-name items. The actual field-content for
-the message-header is stored in the appropriate WSGI collection (either
-the ``environ`` for requests, or ``response_headers`` for responses).
-
-Each ``HTTPHeader`` instance is a callable (defining ``__call__``)
-that takes one of the following:
-
- - an ``environ`` dictionary, returning the corresponding header
- value by according to the WSGI's ``HTTP_`` prefix mechanism, e.g.,
- ``USER_AGENT(environ)`` returns ``environ.get('HTTP_USER_AGENT')``
-
- - a ``response_headers`` list, giving a comma-delimited string for
- each corresponding ``header_value`` tuple entries (see below).
-
- - a sequence of string ``*args`` that are comma-delimited into
- a single string value: ``CONTENT_TYPE("text/html","text/plain")``
- returns ``"text/html, text/plain"``
-
- - a set of ``**kwargs`` keyword arguments that are used to create
- a header value, in a manner dependent upon the particular header in
- question (to make value construction easier and error-free):
- ``CONTENT_DISPOSITION(max_age=CONTENT_DISPOSITION.ONEWEEK)``
- returns ``"public, max-age=60480"``
-
-Each ``HTTPHeader`` instance also provides several methods to act on
-a WSGI collection, for removing and setting header values.
-
- ``delete(collection)``
-
- This method removes all entries of the corresponding header from
- the given collection (``environ`` or ``response_headers``), e.g.,
- ``USER_AGENT.remove(environ)`` deletes the 'HTTP_USER_AGENT' entry
- from the ``environ``.
-
- ``update(collection, *args, **kwargs)``
-
- This method does an in-place replacement of the given header entry,
- for example: ``CONTENT_LENGTH(response_headers,len(body))``
-
- The first argument is a valid ``environ`` dictionary or
- ``response_headers`` list; remaining arguments are passed on to
- ``__call__(*args, **kwargs)`` for value construction.
-
- ``apply(collection, **kwargs)``
-
- This method is similar to update, only that it may affect other
- headers. For example, according to recommendations in RFC 2616,
- certain Cache-Control configurations should also set the
- ``Expires`` header for HTTP/1.0 clients. By default, ``apply()``
- is simply ``update()`` but limited to keyword arguments.
-
-This particular approach to managing headers within a WSGI collection
-has several advantages:
-
- 1. Typos in the header name are easily detected since they become a
- ``NameError`` when executed. The approach of using header strings
- directly can be problematic; for example, the following should
- return ``None`` : ``environ.get("HTTP_ACCEPT_LANGUAGES")``
-
- 2. For specific headers with validation, using ``__call__`` will
- result in an automatic header value check. For example, the
- _ContentDisposition header will reject a value having ``maxage``
- or ``max_age`` (the appropriate parameter is ``max-age`` ).
-
- 3. When appending/replacing headers, the field-name has the suggested
- RFC capitalization (e.g. ``Content-Type`` or ``ETag``) for
- user-agents that incorrectly use case-sensitive matches.
-
- 4. Some headers (such as ``Content-Type``) are 0, that is,
- only one entry of this type may occur in a given set of
- ``response_headers``. This module knows about those cases and
- enforces this cardinality constraint.
-
- 5. The exact details of WSGI header management are abstracted so
- the programmer need not worry about operational differences
- between ``environ`` dictionary or ``response_headers`` list.
-
- 6. Sorting of ``HTTPHeaders`` is done following the RFC suggestion
- that general-headers come first, followed by request and response
- headers, and finishing with entity-headers.
-
- 7. Special care is given to exceptional cases such as Set-Cookie
- which violates the RFC's recommendation about combining header
- content into a single entry using comma separation.
-
-A particular difficulty with HTTP message headers is a categorization
-of sorts as described in section 4.2:
-
- Multiple message-header fields with the same field-name MAY be
- present in a message if and only if the entire field-value for
- that header field is defined as a comma-separated list [i.e.,
- #(values)]. It MUST be possible to combine the multiple header
- fields into one "field-name: field-value" pair, without changing
- the semantics of the message, by appending each subsequent
- field-value to the first, each separated by a comma.
-
-This creates three fundamentally different kinds of headers:
-
- - Those that do not have a #(values) production, and hence are
- singular and may only occur once in a set of response fields;
- this case is handled by the ``_SingleValueHeader`` subclass.
-
- - Those which have the #(values) production and follow the
- combining rule outlined above; our ``_MultiValueHeader`` case.
-
- - Those which are multi-valued, but cannot be combined (such as the
- ``Set-Cookie`` header due to its ``Expires`` parameter); or where
- combining them into a single header entry would cause common
- user-agents to fail (``WWW-Authenticate``, ``Warning``) since
- they fail to handle dates even when properly quoted. This case
- is handled by ``_MultiEntryHeader``.
-
-Since this project does not have time to provide rigorous support
-and validation for all headers, it does a basic construction of
-headers listed in RFC 2616 (plus a few others) so that they can
-be obtained by simply doing ``from paste.httpheaders import *``;
-the name of the header instance is the "common name" less any
-dashes to give CamelCase style names.
-
-.. [1] http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
-.. [2] http://www.python.org/peps/pep-0333.html#environ-variables
-.. [3] http://www.python.org/peps/pep-0333.html#the-start-response-callable
-
-"""
-import urllib2
-import re
-from mimetypes import guess_type
-from rfc822 import formatdate, parsedate_tz, mktime_tz
-from time import time as now
-from httpexceptions import HTTPBadRequest
-
-__all__ = ['get_header', 'list_headers', 'normalize_headers',
- 'HTTPHeader', 'EnvironVariable' ]
-
-class EnvironVariable(str):
- """
- a CGI ``environ`` variable as described by WSGI
-
- This is a helper object so that standard WSGI ``environ`` variables
- can be extracted w/o syntax error possibility.
- """
- def __call__(self, environ):
- return environ.get(self,'')
- def __repr__(self):
- return '<EnvironVariable %s>' % self
- def update(self, environ, value):
- environ[self] = value
-REMOTE_USER = EnvironVariable("REMOTE_USER")
-REMOTE_SESSION = EnvironVariable("REMOTE_SESSION")
-AUTH_TYPE = EnvironVariable("AUTH_TYPE")
-REQUEST_METHOD = EnvironVariable("REQUEST_METHOD")
-SCRIPT_NAME = EnvironVariable("SCRIPT_NAME")
-PATH_INFO = EnvironVariable("PATH_INFO")
-
-for _name, _obj in globals().items():
- if isinstance(_obj, EnvironVariable):
- __all__.append(_name)
-
-_headers = {}
-
-class HTTPHeader(object):
- """
- an HTTP header
-
- HTTPHeader instances represent a particular ``field-name`` of an
- HTTP message header. They do not hold a field-value, but instead
- provide operations that work on is corresponding values. Storage
- of the actual field values is done with WSGI ``environ`` or
- ``response_headers`` as appropriate. Typically, a sub-classes that
- represent a specific HTTP header, such as _ContentDisposition, are
- 0. Once constructed the HTTPHeader instances themselves
- are immutable and stateless.
-
- For purposes of documentation a "container" refers to either a
- WSGI ``environ`` dictionary, or a ``response_headers`` list.
-
- Member variables (and correspondingly constructor arguments).
-
- ``name``
-
- the ``field-name`` of the header, in "common form"
- as presented in RFC 2616; e.g. 'Content-Type'
-
- ``category``
-
- one of 'general', 'request', 'response', or 'entity'
-
- ``version``
-
- version of HTTP (informational) with which the header should
- be recognized
-
- ``sort_order``
-
- sorting order to be applied before sorting on
- field-name when ordering headers in a response
-
- Special Methods:
-
- ``__call__``
-
- The primary method of the HTTPHeader instance is to make
- it a callable, it takes either a collection, a string value,
- or keyword arguments and attempts to find/construct a valid
- field-value
-
- ``__lt__``
-
- This method is used so that HTTPHeader objects can be
- sorted in a manner suggested by RFC 2616.
-
- ``__str__``
-
- The string-value for instances of this class is
- the ``field-name``.
-
- Primary Methods:
-
- ``delete()``
-
- remove the all occurrences (if any) of the given
- header in the collection provided
-
- ``update()``
-
- replaces (if they exist) all field-value items
- in the given collection with the value provided
-
- ``tuples()``
-
- returns a set of (field-name, field-value) tuples
- 5 for extending ``response_headers``
-
- Custom Methods (these may not be implemented):
-
- ``apply()``
-
- similar to ``update``, but with two differences; first,
- only keyword arguments can be used, and second, specific
- sub-classes may introduce side-effects
-
- ``parse()``
-
- converts a string value of the header into a more usable
- form, such as time in seconds for a date header, etc.
-
- The collected versions of initialized header instances are immediately
- registered and accessible through the ``get_header`` function. Do not
- inherit from this directly, use one of ``_SingleValueHeader``,
- ``_MultiValueHeader``, or ``_MultiEntryHeader`` as appropriate.
- """
-
- #
- # Things which can be customized
- #
- version = '1.1'
- category = 'general'
- reference = ''
- extensions = {}
-
- def compose(self, **kwargs):
- """
- build header value from keyword arguments
-
- This method is used to build the corresponding header value when
- keyword arguments (or no arguments) were provided. The result
- should be a sequence of values. For example, the ``Expires``
- header takes a keyword argument ``time`` (e.g. time.time()) from
- which it returns a the corresponding date.
- """
- raise NotImplementedError()
-
- def parse(self, *args, **kwargs):
- """
- convert raw header value into more usable form
-
- This method invokes ``values()`` with the arguments provided,
- parses the header results, and then returns a header-specific
- data structure corresponding to the header. For example, the
- ``Expires`` header returns seconds (as returned by time.time())
- """
- raise NotImplementedError()
-
- def apply(self, collection, **kwargs):
- """
- update the collection /w header value (may have side effects)
-
- This method is similar to ``update`` only that usage may result
- in other headers being changed as recommended by the corresponding
- specification. The return value is defined by the particular
- sub-class. For example, the ``_CacheControl.apply()`` sets the
- ``Expires`` header in addition to its normal behavior.
- """
- self.update(collection, **kwargs)
-
- #
- # Things which are standardized (mostly)
- #
- def __new__(cls, name, category=None, reference=None, version=None):
- """
- construct a new ``HTTPHeader`` instance
-
- We use the ``__new__`` operator to ensure that only one
- ``HTTPHeader`` instance exists for each field-name, and to
- register the header so that it can be found/enumerated.
- """
- self = get_header(name, raiseError=False)
- if self:
- # Allow the registration to happen again, but assert
- # that everything is identical.
- assert self.name == name, \
- "duplicate registration with different capitalization"
- assert self.category == category, \
- "duplicate registration with different category"
- assert cls == self.__class__, \
- "duplicate registration with different class"
- return self
-
- self = object.__new__(cls)
- self.name = name
- assert isinstance(self.name, str)
- self.category = category or self.category
- self.version = version or self.version
- self.reference = reference or self.reference
- _headers[self.name.lower()] = self
- self.sort_order = {'general': 1, 'request': 2,
- 'response': 3, 'entity': 4 }[self.category]
- self._environ_name = getattr(self, '_environ_name',
- 'HTTP_'+ self.name.upper().replace("-","_"))
- self._headers_name = getattr(self, '_headers_name',
- self.name.lower())
- assert self.version in ('1.1', '1.0', '0.9')
- return self
-
- def __str__(self):
- return self.name
-
- def __lt__(self, other):
- """
- sort header instances as specified by RFC 2616
-
- Re-define sorting so that general headers are first, followed
- by request/response headers, and then entity headers. The
- list.sort() methods use the less-than operator for this purpose.
- """
- if isinstance(other, HTTPHeader):
- if self.sort_order != other.sort_order:
- return self.sort_order < other.sort_order
- return self.name < other.name
- return False
-
- def __repr__(self):
- ref = self.reference and (' (%s)' % self.reference) or ''
- return '<%s %s%s>' % (self.__class__.__name__, self.name, ref)
-
- def values(self, *args, **kwargs):
- """
- find/construct field-value(s) for the given header
-
- Resolution is done according to the following arguments:
-
- - If only keyword arguments are given, then this is equivalent
- to ``compose(**kwargs)``.
-
- - If the first (and only) argument is a dict, it is assumed
- to be a WSGI ``environ`` and the result of the corresponding
- ``HTTP_`` entry is returned.
-
- - If the first (and only) argument is a list, it is assumed
- to be a WSGI ``response_headers`` and the field-value(s)
- for this header are collected and returned.
-
- - In all other cases, the arguments are collected, checked that
- they are string values, possibly verified by the header's
- logic, and returned.
-
- At this time it is an error to provide keyword arguments if args
- is present (this might change). It is an error to provide both
- a WSGI object and also string arguments. If no arguments are
- provided, then ``compose()`` is called to provide a default
- value for the header; if there is not default it is an error.
- """
- if not args:
- return self.compose(**kwargs)
- if list == type(args[0]):
- assert 1 == len(args)
- result = []
- name = self.name.lower()
- for value in [value for header, value in args[0]
- if header.lower() == name]:
- result.append(value)
- return result
- if dict == type(args[0]):
- assert 1 == len(args) and 'wsgi.version' in args[0]
- value = args[0].get(self._environ_name)
- if not value:
- return ()
- return (value,)
- for item in args:
- assert not type(item) in (dict, list)
- return args
-
- def __call__(self, *args, **kwargs):
- """
- converts ``values()`` into a string value
-
- This method converts the results of ``values()`` into a string
- value for common usage. By default, it is asserted that only
- one value exists; if you need to access all values then either
- call ``values()`` directly, or inherit ``_MultiValueHeader``
- which overrides this method to return a comma separated list of
- values as described by section 4.2 of RFC 2616.
- """
- values = self.values(*args, **kwargs)
- assert isinstance(values, (tuple, list))
- if not values:
- return ''
- assert len(values) == 1, "more than one value: %s" % repr(values)
- return str(values[0]).strip()
-
- def delete(self, collection):
- """
- removes all occurances of the header from the collection provided
- """
- if type(collection) == dict:
- if self._environ_name in collection:
- del collection[self._environ_name]
- return self
- assert list == type(collection)
- i = 0
- while i < len(collection):
- if collection[i][0].lower() == self._headers_name:
- del collection[i]
- continue
- i += 1
-
- def update(self, collection, *args, **kwargs):
- """
- updates the collection with the provided header value
-
- This method replaces (in-place when possible) all occurrences of
- the given header with the provided value. If no value is
- provided, this is the same as ``remove`` (note that this case
- can only occur if the target is a collection w/o a corresponding
- header value). The return value is the new header value (which
- could be a list for ``_MultiEntryHeader`` instances).
- """
- value = self.__call__(*args, **kwargs)
- if not value:
- self.remove(collection)
- return
- if type(collection) == dict:
- collection[self._environ_name] = value
- return
- assert list == type(collection)
- i = 0
- found = False
- while i < len(collection):
- if collection[i][0].lower() == self._headers_name:
- if found:
- del collection[i]
- continue
- collection[i] = (self.name, value)
- found = True
- i += 1
- if not found:
- collection.append((self.name, value))
-
- def tuples(self, *args, **kwargs):
- value = self.__call__(*args, **kwargs)
- if not value:
- return ()
- return [(self.name, value)]
-
-class _SingleValueHeader(HTTPHeader):
- """
- a ``HTTPHeader`` with exactly a single value
-
- This is the default behavior of ``HTTPHeader`` where returning a
- the string-value of headers via ``__call__`` assumes that only
- a single value exists.
- """
- pass
-
-class _MultiValueHeader(HTTPHeader):
- """
- a ``HTTPHeader`` with one or more values
-
- The field-value for these header instances is is allowed to be more
- than one value; whereby the ``__call__`` method returns a comma
- separated list as described by section 4.2 of RFC 2616.
- """
-
- def __call__(self, *args, **kwargs):
- results = self.values(*args, **kwargs)
- if not results:
- return ''
- return ", ".join([str(v).strip() for v in results])
-
- def parse(self, *args, **kwargs):
- value = self.__call__(*args, **kwargs)
- values = value.split(',')
- return [
- v.strip() for v in values
- if v.strip()]
-
-class _MultiEntryHeader(HTTPHeader):
- """
- a multi-value ``HTTPHeader`` where items cannot be combined with a comma
-
- This header is multi-valued, but the values should not be combined
- with a comma since the header is not in compliance with RFC 2616
- (Set-Cookie due to Expires parameter) or which common user-agents do
- not behave well when the header values are combined.
- """
-
- def update(self, collection, *args, **kwargs):
- assert list == type(collection), "``environ`` may not be updated"
- self.delete(collection)
- collection.extend(self.tuples(*args, **kwargs))
-
- def tuples(self, *args, **kwargs):
- values = self.values(*args, **kwargs)
- if not values:
- return ()
- return [(self.name, value.strip()) for value in values]
-
-def get_header(name, raiseError=True):
- """
- find the given ``HTTPHeader`` instance
-
- This function finds the corresponding ``HTTPHeader`` for the
- ``name`` provided. So that python-style names can be used,
- underscores are converted to dashes before the lookup.
- """
- retval = _headers.get(str(name).strip().lower().replace("_","-"))
- if not retval and raiseError:
- raise AssertionError("'%s' is an unknown header" % name)
- return retval
-
-def list_headers(general=None, request=None, response=None, entity=None):
- " list all headers for a given category "
- if not (general or request or response or entity):
- general = request = response = entity = True
- search = []
- for (bool, strval) in ((general, 'general'), (request, 'request'),
- (response, 'response'), (entity, 'entity')):
- if bool:
- search.append(strval)
- return [head for head in _headers.values() if head.category in search]
-
-def normalize_headers(response_headers, strict=True):
- """
- sort headers as suggested by RFC 2616
-
- This alters the underlying response_headers to use the common
- name for each header; as well as sorting them with general
- headers first, followed by request/response headers, then
- entity headers, and unknown headers last.
- """
- category = {}
- for idx in range(len(response_headers)):
- (key, val) = response_headers[idx]
- head = get_header(key, strict)
- if not head:
- newhead = '-'.join([x.capitalize() for x in
- key.replace("_","-").split("-")])
- response_headers[idx] = (newhead, val)
- category[newhead] = 4
- 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)
-
-class _DateHeader(_SingleValueHeader):
- """
- handle date-based headers
-
- This extends the ``_SingleValueHeader`` object with specific
- treatment of time values:
-
- - It overrides ``compose`` to provide a sole keyword argument
- ``time`` which is an offset in seconds from the current time.
-
- - A ``time`` method is provided which parses the given value
- and returns the current time value.
- """
-
- def compose(self, time=None, delta=None):
- time = time or now()
- if delta:
- assert type(delta) == int
- time += delta
- return (formatdate(time),)
-
- def parse(self, *args, **kwargs):
- """ return the time value (in seconds since 1970) """
- value = self.__call__(*args, **kwargs)
- if value:
- try:
- return mktime_tz(parsedate_tz(value))
- except TypeError:
- raise HTTPBadRequest((
- "Received an ill-formed timestamp for %s: %s\r\n") %
- (self.name, value))
-
-#
-# Following are specific HTTP headers. Since these classes are mostly
-# singletons, there is no point in keeping the class around once it has
-# been instantiated, so we use the same name.
-#
-
-class _CacheControl(_MultiValueHeader):
- """
- Cache-Control, RFC 2616 14.9 (use ``CACHE_CONTROL``)
-
- This header can be constructed (using keyword arguments), by
- first specifying one of the following mechanisms:
-
- ``public``
-
- if True, this argument specifies that the
- response, as a whole, may be cashed.
-
- ``private``
-
- if True, this argument specifies that the response, as a
- whole, may be cashed; this implementation does not support
- the enumeration of private fields
-
- ``no_cache``
-
- if True, this argument specifies that the response, as a
- whole, may not be cashed; this implementation does not
- support the enumeration of private fields
-
- In general, only one of the above three may be True, the other 2
- must then be False or None. If all three are None, then the cache
- is assumed to be ``public``. Following one of these mechanism
- specifiers are various modifiers:
-
- ``no_store``
-
- indicates if content may be stored on disk;
- otherwise cache is limited to memory (note:
- users can still save the data, this applies
- to intermediate caches)
-
- ``max_age``
-
- the maximum duration (in seconds) for which
- the content should be cached; if ``no-cache``
- is specified, this defaults to 0 seconds
-
- ``s_maxage``
-
- the maximum duration (in seconds) for which the
- content should be allowed in a shared cache.
-
- ``no_transform``
-
- specifies that an intermediate cache should
- not convert the content from one type to
- another (e.g. transform a BMP to a PNG).
-
- ``extensions``
-
- gives additional cache-control extensions,
- such as items like, community="UCI" (14.9.6)
-
- The usage of ``apply()`` on this header has side-effects. As
- recommended by RFC 2616, if ``max_age`` is provided, then then the
- ``Expires`` header is also calculated for HTTP/1.0 clients and
- proxies (this is done at the time ``apply()`` is called). For
- ``no-cache`` and for ``private`` cases, we either do not want the
- response cached or do not want any response accidently returned to
- other users; so to prevent this case, we set the ``Expires`` header
- to the time of the request, signifying to HTTP/1.0 transports that
- the content isn't to be cached. If you are using SSL, your
- communication is already "private", so to work with HTTP/1.0
- browsers over SSL, consider specifying your cache as ``public`` as
- the distinction between public and private is moot.
- """
-
- # common values for max-age; "good enough" approximates
- ONE_HOUR = 60*60
- ONE_DAY = ONE_HOUR * 24
- ONE_WEEK = ONE_DAY * 7
- ONE_MONTH = ONE_DAY * 30
- ONE_YEAR = ONE_WEEK * 52
-
- def _compose(self, public=None, private=None, no_cache=None,
- no_store=False, max_age=None, s_maxage=None,
- no_transform=False, **extensions):
- assert isinstance(max_age, (type(None), int))
- assert isinstance(s_maxage, (type(None), int))
- expires = 0
- result = []
- if private is True:
- assert not public and not no_cache and not s_maxage
- result.append('private')
- elif no_cache is True:
- assert not public and not private and not max_age
- result.append('no-cache')
- else:
- assert public is None or public is True
- assert not private and not no_cache
- expires = max_age
- result.append('public')
- if no_store:
- result.append('no-store')
- if no_transform:
- result.append('no-transform')
- if max_age is not None:
- result.append('max-age=%d' % max_age)
- if s_maxage is not None:
- result.append('s-maxage=%d' % s_maxage)
- for (k, v) in extensions.items():
- if k not in self.extensions:
- raise AssertionError("unexpected extension used: '%s'" % k)
- result.append('%s="%s"' % (k.replace("_", "-"), v))
- return (result, expires)
-
- def compose(self, **kwargs):
- (result, expires) = self._compose(**kwargs)
- return result
-
- def apply(self, collection, **kwargs):
- """ returns the offset expiration in seconds """
- (result, expires) = self._compose(**kwargs)
- if expires is not None:
- EXPIRES.update(collection, delta=expires)
- self.update(collection, *result)
- return expires
-
-_CacheControl('Cache-Control', 'general', 'RFC 2616, 14.9')
-
-class _ContentType(_SingleValueHeader):
- """
- Content-Type, RFC 2616 section 14.17
-
- Unlike other headers, use the CGI variable instead.
- """
- version = '1.0'
- _environ_name = 'CONTENT_TYPE'
-
- # common mimetype constants
- UNKNOWN = 'application/octet-stream'
- TEXT_PLAIN = 'text/plain'
- TEXT_HTML = 'text/html'
- TEXT_XML = 'text/xml'
-
- def compose(self, major=None, minor=None, charset=None):
- if not major:
- if minor in ('plain', 'html', 'xml'):
- major = 'text'
- else:
- assert not minor and not charset
- return (self.UNKNOWN,)
- if not minor:
- minor = "*"
- result = "%s/%s" % (major, minor)
- if charset:
- result += "; charset=%s" % charset
- return (result,)
-
-_ContentType('Content-Type', 'entity', 'RFC 2616, 14.17')
-
-class _ContentLength(_SingleValueHeader):
- """
- Content-Length, RFC 2616 section 14.13
-
- Unlike other headers, use the CGI variable instead.
- """
- version = "1.0"
- _environ_name = 'CONTENT_LENGTH'
-
-_ContentLength('Content-Length', 'entity', 'RFC 2616, 14.13')
-
-class _ContentDisposition(_SingleValueHeader):
- """
- Content-Disposition, RFC 2183 (use ``CONTENT_DISPOSITION``)
-
- This header can be constructed (using keyword arguments),
- by first specifying one of the following mechanisms:
-
- ``attachment``
-
- if True, this specifies that the content should not be
- shown in the browser and should be handled externally,
- even if the browser could render the content
-
- ``inline``
-
- exclusive with attachment; indicates that the content
- should be rendered in the browser if possible, but
- otherwise it should be handled externally
-
- Only one of the above 2 may be True. If both are None, then
- the disposition is assumed to be an ``attachment``. These are
- distinct fields since support for field enumeration may be
- added in the future.
-
- ``filename``
-
- the filename parameter, if any, to be reported; if
- this is None, then the current object's filename
- attribute is used
-
- The usage of ``apply()`` on this header has side-effects. If
- filename is provided, and Content-Type is not set or is
- 'application/octet-stream', then the mimetypes.guess is used to
- upgrade the Content-Type setting.
- """
-
- def _compose(self, attachment=None, inline=None, filename=None):
- result = []
- if inline is True:
- assert not attachment
- result.append('inline')
- else:
- assert not inline
- result.append('attachment')
- if filename:
- assert '"' not in filename
- filename = filename.split("/")[-1]
- filename = filename.split("\\")[-1]
- result.append('filename="%s"' % filename)
- return (("; ".join(result),), filename)
-
- def compose(self, **kwargs):
- (result, mimetype) = self._compose(**kwargs)
- return result
-
- def apply(self, collection, **kwargs):
- """ return the new Content-Type side-effect value """
- (result, filename) = self._compose(**kwargs)
- mimetype = CONTENT_TYPE(collection)
- if filename and (not mimetype or CONTENT_TYPE.UNKNOWN == mimetype):
- mimetype, _ = guess_type(filename)
- if mimetype and CONTENT_TYPE.UNKNOWN != mimetype:
- CONTENT_TYPE.update(collection, mimetype)
- self.update(collection, *result)
- return mimetype
-
-_ContentDisposition('Content-Disposition', 'entity', 'RFC 2183')
-
-class _IfModifiedSince(_DateHeader):
- """
- If-Modified-Since, RFC 2616 section 14.25
- """
- version = '1.0'
-
- def __call__(self, *args, **kwargs):
- """
- Split the value on ';' incase the header includes extra attributes. E.g.
- IE 6 is known to send:
- If-Modified-Since: Sun, 25 Jun 2006 20:36:35 GMT; length=1506
- """
- return _DateHeader.__call__(self, *args, **kwargs).split(';', 1)[0]
-
- def parse(self, *args, **kwargs):
- value = _DateHeader.parse(self, *args, **kwargs)
- if value and value > now():
- raise HTTPBadRequest((
- "Please check your system clock.\r\n"
- "According to this server, the time provided in the\r\n"
- "%s header is in the future.\r\n") % self.name)
- return value
-_IfModifiedSince('If-Modified-Since', 'request', 'RFC 2616, 14.25')
-
-class _Range(_MultiValueHeader):
- """
- Range, RFC 2616 14.35 (use ``RANGE``)
-
- According to section 14.16, the response to this message should be a
- 206 Partial Content and that if multiple non-overlapping byte ranges
- are requested (it is an error to request multiple overlapping
- ranges) the result should be sent as multipart/byteranges mimetype.
-
- The server should respond with '416 Requested Range Not Satisfiable'
- if the requested ranges are out-of-bounds. The specification also
- indicates that a syntax error in the Range request should result in
- the header being ignored rather than a '400 Bad Request'.
- """
-
- def parse(self, *args, **kwargs):
- """
- Returns a tuple (units, list), where list is a sequence of
- (begin, end) tuples; and end is None if it was not provided.
- """
- value = self.__call__(*args, **kwargs)
- if not value:
- return None
- ranges = []
- last_end = -1
- try:
- (units, range) = value.split("=", 1)
- units = units.strip().lower()
- for item in range.split(","):
- (begin, end) = item.split("-")
- if not begin.strip():
- begin = 0
- else:
- begin = int(begin)
- if begin <= last_end:
- raise ValueError()
- if not end.strip():
- end = None
- else:
- end = int(end)
- last_end = end
- ranges.append((begin, end))
- except ValueError:
- # In this case where the Range header is malformed,
- # section 14.16 says to treat the request as if the
- # Range header was not present. How do I log this?
- return None
- return (units, ranges)
-_Range('Range', 'request', 'RFC 2616, 14.35')
-
-class _AcceptLanguage(_MultiValueHeader):
- """
- Accept-Language, RFC 2616 section 14.4
- """
-
- def parse(self, *args, **kwargs):
- """
- Return a list of language tags sorted by their "q" values. For example,
- "en-us,en;q=0.5" should return ``["en-us", "en"]``. If there is no
- ``Accept-Language`` header present, default to ``[]``.
- """
- header = self.__call__(*args, **kwargs)
- if header is None:
- return []
- langs = [v for v in header.split(",") if v]
- qs = []
- for lang in langs:
- pieces = lang.split(";")
- lang, params = pieces[0].strip().lower(), pieces[1:]
- q = 1
- for param in params:
- if '=' not in param:
- # Malformed request; probably a bot, we'll ignore
- continue
- lvalue, rvalue = param.split("=")
- lvalue = lvalue.strip().lower()
- rvalue = rvalue.strip()
- if lvalue == "q":
- q = float(rvalue)
- qs.append((lang, q))
- qs.sort(lambda a, b: -cmp(a[1], b[1]))
- return [lang for (lang, q) in qs]
-_AcceptLanguage('Accept-Language', 'request', 'RFC 2616, 14.4')
-
-class _AcceptRanges(_MultiValueHeader):
- """
- Accept-Ranges, RFC 2616 section 14.5
- """
- def compose(self, none=None, bytes=None):
- if bytes:
- return ('bytes',)
- return ('none',)
-_AcceptRanges('Accept-Ranges', 'response', 'RFC 2616, 14.5')
-
-class _ContentRange(_SingleValueHeader):
- """
- Content-Range, RFC 2616 section 14.6
- """
- def compose(self, first_byte=None, last_byte=None, total_length=None):
- retval = "bytes %d-%d/%d" % (first_byte, last_byte, total_length)
- assert last_byte == -1 or first_byte <= last_byte
- assert last_byte < total_length
- return (retval,)
-_ContentRange('Content-Range', 'entity', 'RFC 2616, 14.6')
-
-class _Authorization(_SingleValueHeader):
- """
- Authorization, RFC 2617 (RFC 2616, 14.8)
- """
- def compose(self, digest=None, basic=None, username=None, password=None,
- challenge=None, path=None, method=None):
- assert username and password
- if basic or not challenge:
- assert not digest
- userpass = "%s:%s" % (username.strip(), password.strip())
- return "Basic %s" % userpass.encode('base64').strip()
- assert challenge and not basic
- path = path or "/"
- (_, realm) = challenge.split('realm="')
- (realm, _) = realm.split('"', 1)
- auth = urllib2.AbstractDigestAuthHandler()
- auth.add_password(realm, path, username, password)
- (token, challenge) = challenge.split(' ', 1)
- chal = urllib2.parse_keqv_list(urllib2.parse_http_list(challenge))
- class FakeRequest(object):
- def get_full_url(self):
- return path
- 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')
-
-#
-# For now, construct a minimalistic version of the field-names; at a
-# later date more complicated headers may sprout content constructors.
-# The items commented out have concrete variants.
-#
-for (name, category, version, style, comment) in \
-(("Accept" ,'request' ,'1.1','multi-value','RFC 2616, 14.1' )
-,("Accept-Charset" ,'request' ,'1.1','multi-value','RFC 2616, 14.2' )
-,("Accept-Encoding" ,'request' ,'1.1','multi-value','RFC 2616, 14.3' )
-#,("Accept-Language" ,'request' ,'1.1','multi-value','RFC 2616, 14.4' )
-#,("Accept-Ranges" ,'response','1.1','multi-value','RFC 2616, 14.5' )
-,("Age" ,'response','1.1','singular' ,'RFC 2616, 14.6' )
-,("Allow" ,'entity' ,'1.0','multi-value','RFC 2616, 14.7' )
-#,("Authorization" ,'request' ,'1.0','singular' ,'RFC 2616, 14.8' )
-#,("Cache-Control" ,'general' ,'1.1','multi-value','RFC 2616, 14.9' )
-,("Cookie" ,'request' ,'1.0','multi-value','RFC 2109/Netscape')
-,("Connection" ,'general' ,'1.1','multi-value','RFC 2616, 14.10')
-,("Content-Encoding" ,'entity' ,'1.0','multi-value','RFC 2616, 14.11')
-#,("Content-Disposition",'entity' ,'1.1','multi-value','RFC 2616, 15.5' )
-,("Content-Language" ,'entity' ,'1.1','multi-value','RFC 2616, 14.12')
-#,("Content-Length" ,'entity' ,'1.0','singular' ,'RFC 2616, 14.13')
-,("Content-Location" ,'entity' ,'1.1','singular' ,'RFC 2616, 14.14')
-,("Content-MD5" ,'entity' ,'1.1','singular' ,'RFC 2616, 14.15')
-#,("Content-Range" ,'entity' ,'1.1','singular' ,'RFC 2616, 14.16')
-#,("Content-Type" ,'entity' ,'1.0','singular' ,'RFC 2616, 14.17')
-,("Date" ,'general' ,'1.0','date-header','RFC 2616, 14.18')
-,("ETag" ,'response','1.1','singular' ,'RFC 2616, 14.19')
-,("Expect" ,'request' ,'1.1','multi-value','RFC 2616, 14.20')
-,("Expires" ,'entity' ,'1.0','date-header','RFC 2616, 14.21')
-,("From" ,'request' ,'1.0','singular' ,'RFC 2616, 14.22')
-,("Host" ,'request' ,'1.1','singular' ,'RFC 2616, 14.23')
-,("If-Match" ,'request' ,'1.1','multi-value','RFC 2616, 14.24')
-#,("If-Modified-Since" ,'request' ,'1.0','date-header','RFC 2616, 14.25')
-,("If-None-Match" ,'request' ,'1.1','multi-value','RFC 2616, 14.26')
-,("If-Range" ,'request' ,'1.1','singular' ,'RFC 2616, 14.27')
-,("If-Unmodified-Since",'request' ,'1.1','date-header' ,'RFC 2616, 14.28')
-,("Last-Modified" ,'entity' ,'1.0','date-header','RFC 2616, 14.29')
-,("Location" ,'response','1.0','singular' ,'RFC 2616, 14.30')
-,("Max-Forwards" ,'request' ,'1.1','singular' ,'RFC 2616, 14.31')
-,("Pragma" ,'general' ,'1.0','multi-value','RFC 2616, 14.32')
-,("Proxy-Authenticate" ,'response','1.1','multi-value','RFC 2616, 14.33')
-,("Proxy-Authorization",'request' ,'1.1','singular' ,'RFC 2616, 14.34')
-#,("Range" ,'request' ,'1.1','multi-value','RFC 2616, 14.35')
-,("Referer" ,'request' ,'1.0','singular' ,'RFC 2616, 14.36')
-,("Retry-After" ,'response','1.1','singular' ,'RFC 2616, 14.37')
-,("Server" ,'response','1.0','singular' ,'RFC 2616, 14.38')
-,("Set-Cookie" ,'response','1.0','multi-entry','RFC 2109/Netscape')
-,("TE" ,'request' ,'1.1','multi-value','RFC 2616, 14.39')
-,("Trailer" ,'general' ,'1.1','multi-value','RFC 2616, 14.40')
-,("Transfer-Encoding" ,'general' ,'1.1','multi-value','RFC 2616, 14.41')
-,("Upgrade" ,'general' ,'1.1','multi-value','RFC 2616, 14.42')
-,("User-Agent" ,'request' ,'1.0','singular' ,'RFC 2616, 14.43')
-,("Vary" ,'response','1.1','multi-value','RFC 2616, 14.44')
-,("Via" ,'general' ,'1.1','multi-value','RFC 2616, 14.45')
-,("Warning" ,'general' ,'1.1','multi-entry','RFC 2616, 14.46')
-,("WWW-Authenticate" ,'response','1.0','multi-entry','RFC 2616, 14.47')):
- klass = {'multi-value': _MultiValueHeader,
- 'multi-entry': _MultiEntryHeader,
- 'date-header': _DateHeader,
- 'singular' : _SingleValueHeader}[style]
- klass(name, category, comment, version).__doc__ = comment
- del klass
-
-for head in _headers.values():
- headname = head.name.replace("-","_").upper()
- locals()[headname] = head
- __all__.append(headname)
-
-__pudge_all__ = __all__[:]
-for _name, _obj in globals().items():
- if isinstance(_obj, type) and issubclass(_obj, HTTPHeader):
- __pudge_all__.append(_name)
diff --git a/lib/paste/httpserver.py b/lib/paste/httpserver.py
@@ -1,1406 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-WSGI HTTP Server
-
-This is a minimalistic WSGI server using Python's built-in BaseHTTPServer;
-if pyOpenSSL is installed, it also provides SSL capabilities.
-"""
-
-# @@: add in protection against HTTP/1.0 clients who claim to
-# be 1.1 but do not send a Content-Length
-
-# @@: add support for chunked encoding, this is not a 1.1 server
-# till this is completed.
-
-import atexit
-import traceback
-import socket, sys, threading, urlparse, Queue, urllib
-import posixpath
-import time
-import thread
-import os
-from itertools import count
-from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
-from SocketServer import ThreadingMixIn
-from paste.util import converters
-import logging
-try:
- from paste.util import killthread
-except ImportError:
- # Not available, probably no ctypes
- killthread = None
-
-__all__ = ['WSGIHandlerMixin', 'WSGIServer', 'WSGIHandler', 'serve']
-__version__ = "0.5"
-
-class ContinueHook(object):
- """
- When a client request includes a 'Expect: 100-continue' header, then
- it is the responsibility of the server to send 100 Continue when it
- is ready for the content body. This allows authentication, access
- levels, and other exceptions to be detected *before* bandwith is
- spent on the request body.
-
- This is a rfile wrapper that implements this functionality by
- sending 100 Continue to the client immediately after the user
- requests the content via a read() operation on the rfile stream.
- After this response is sent, it becomes a pass-through object.
- """
-
- def __init__(self, rfile, write):
- self._ContinueFile_rfile = rfile
- self._ContinueFile_write = write
- for attr in ('close', 'closed', 'fileno', 'flush',
- 'mode', 'bufsize', 'softspace'):
- if hasattr(rfile, attr):
- setattr(self, attr, getattr(rfile, attr))
- for attr in ('read', 'readline', 'readlines'):
- if hasattr(rfile, attr):
- setattr(self, attr, getattr(self, '_ContinueFile_' + attr))
-
- def _ContinueFile_send(self):
- self._ContinueFile_write("HTTP/1.1 100 Continue\r\n\r\n")
- rfile = self._ContinueFile_rfile
- for attr in ('read', 'readline', 'readlines'):
- if hasattr(rfile, attr):
- setattr(self, attr, getattr(rfile, attr))
-
- def _ContinueFile_read(self, size=-1):
- self._ContinueFile_send()
- return self._ContinueFile_rfile.readline(size)
-
- def _ContinueFile_readline(self, size=-1):
- self._ContinueFile_send()
- return self._ContinueFile_rfile.readline(size)
-
- def _ContinueFile_readlines(self, sizehint=0):
- self._ContinueFile_send()
- return self._ContinueFile_rfile.readlines(sizehint)
-
-class WSGIHandlerMixin:
- """
- WSGI mix-in for HTTPRequestHandler
-
- This class is a mix-in to provide WSGI functionality to any
- HTTPRequestHandler derivative (as provided in Python's BaseHTTPServer).
- This assumes a ``wsgi_application`` handler on ``self.server``.
- """
- lookup_addresses = True
-
- def log_request(self, *args, **kwargs):
- """ disable success request logging
-
- Logging transactions should not be part of a WSGI server,
- if you want logging; look at paste.translogger
- """
- pass
-
- def log_message(self, *args, **kwargs):
- """ disable error message logging
-
- Logging transactions should not be part of a WSGI server,
- if you want logging; look at paste.translogger
- """
- pass
-
- def version_string(self):
- """ behavior that BaseHTTPServer should have had """
- if not self.sys_version:
- return self.server_version
- else:
- return self.server_version + ' ' + self.sys_version
-
- def wsgi_write_chunk(self, chunk):
- """
- Write a chunk of the output stream; send headers if they
- have not already been sent.
- """
- if not self.wsgi_headers_sent and not self.wsgi_curr_headers:
- raise RuntimeError(
- "Content returned before start_response called")
- if not self.wsgi_headers_sent:
- self.wsgi_headers_sent = True
- (status, headers) = self.wsgi_curr_headers
- code, message = status.split(" ", 1)
- self.send_response(int(code), message)
- #
- # HTTP/1.1 compliance; either send Content-Length or
- # signal that the connection is being closed.
- #
- send_close = True
- for (k, v) in headers:
- lk = k.lower()
- if 'content-length' == lk:
- send_close = False
- if 'connection' == lk:
- if 'close' == v.lower():
- self.close_connection = 1
- send_close = False
- self.send_header(k, v)
- if send_close:
- self.close_connection = 1
- self.send_header('Connection', 'close')
-
- self.end_headers()
- self.wfile.write(chunk)
-
- def wsgi_start_response(self, status, response_headers, exc_info=None):
- if exc_info:
- try:
- if self.wsgi_headers_sent:
- raise exc_info[0], exc_info[1], exc_info[2]
- else:
- # In this case, we're going to assume that the
- # higher-level code is currently handling the
- # issue and returning a resonable response.
- # self.log_error(repr(exc_info))
- pass
- finally:
- exc_info = None
- elif self.wsgi_curr_headers:
- assert 0, "Attempt to set headers a second time w/o an exc_info"
- self.wsgi_curr_headers = (status, response_headers)
- return self.wsgi_write_chunk
-
- def wsgi_setup(self, environ=None):
- """
- Setup the member variables used by this WSGI mixin, including
- the ``environ`` and status member variables.
-
- After the basic environment is created; the optional ``environ``
- argument can be used to override any settings.
- """
-
- (scheme, netloc, path, query, fragment) = urlparse.urlsplit(self.path)
- path = urllib.unquote(path)
- endslash = path.endswith('/')
- path = posixpath.normpath(path)
- if endslash and path != '/':
- # Put the slash back...
- path += '/'
- (server_name, server_port) = self.server.server_address
-
- rfile = self.rfile
- if 'HTTP/1.1' == self.protocol_version and \
- '100-continue' == self.headers.get('Expect','').lower():
- rfile = ContinueHook(rfile, self.wfile.write)
- else:
- # We can put in the protection to keep from over-reading the
- # file
- try:
- content_length = int(self.headers.get('Content-Length', '0'))
- except ValueError:
- content_length = 0
- if not hasattr(self.connection, 'get_context'):
- # @@: LimitedLengthFile is currently broken in connection
- # with SSL (sporatic errors that are diffcult to trace, but
- # ones that go away when you don't use LimitedLengthFile)
- rfile = LimitedLengthFile(rfile, content_length)
-
- remote_address = self.client_address[0]
- self.wsgi_environ = {
- 'wsgi.version': (1,0)
- ,'wsgi.url_scheme': 'http'
- ,'wsgi.input': rfile
- ,'wsgi.errors': sys.stderr
- ,'wsgi.multithread': True
- ,'wsgi.multiprocess': False
- ,'wsgi.run_once': False
- # CGI variables required by PEP-333
- ,'REQUEST_METHOD': self.command
- ,'SCRIPT_NAME': '' # application is root of server
- ,'PATH_INFO': path
- ,'QUERY_STRING': query
- ,'CONTENT_TYPE': self.headers.get('Content-Type', '')
- ,'CONTENT_LENGTH': self.headers.get('Content-Length', '0')
- ,'SERVER_NAME': server_name
- ,'SERVER_PORT': str(server_port)
- ,'SERVER_PROTOCOL': self.request_version
- # CGI not required by PEP-333
- ,'REMOTE_ADDR': remote_address
- }
- if scheme:
- self.wsgi_environ['paste.httpserver.proxy.scheme'] = scheme
- if netloc:
- self.wsgi_environ['paste.httpserver.proxy.host'] = netloc
-
- if self.lookup_addresses:
- # @@: make lookup_addreses actually work, at this point
- # it has been address_string() is overriden down in
- # file and hence is a noop
- if remote_address.startswith("192.168.") \
- or remote_address.startswith("10.") \
- or remote_address.startswith("172.16."):
- pass
- else:
- address_string = None # self.address_string()
- if address_string:
- self.wsgi_environ['REMOTE_HOST'] = address_string
-
- if hasattr(self.server, 'thread_pool'):
- # Now that we know what the request was for, we should
- # tell the thread pool what its worker is working on
- self.server.thread_pool.worker_tracker[thread.get_ident()][1] = self.wsgi_environ
- self.wsgi_environ['paste.httpserver.thread_pool'] = self.server.thread_pool
-
- for k, v in self.headers.items():
- key = 'HTTP_' + k.replace("-","_").upper()
- if key in ('HTTP_CONTENT_TYPE','HTTP_CONTENT_LENGTH'):
- continue
- self.wsgi_environ[key] = ','.join(self.headers.getheaders(k))
-
- if hasattr(self.connection,'get_context'):
- self.wsgi_environ['wsgi.url_scheme'] = 'https'
- # @@: extract other SSL parameters from pyOpenSSL at...
- # http://www.modssl.org/docs/2.8/ssl_reference.html#ToC25
-
- if environ:
- assert isinstance(environ, dict)
- self.wsgi_environ.update(environ)
- if 'on' == environ.get('HTTPS'):
- self.wsgi_environ['wsgi.url_scheme'] = 'https'
-
- self.wsgi_curr_headers = None
- self.wsgi_headers_sent = False
-
- def wsgi_connection_drop(self, exce, environ=None):
- """
- Override this if you're interested in socket exceptions, such
- as when the user clicks 'Cancel' during a file download.
- """
- pass
-
- def wsgi_execute(self, environ=None):
- """
- Invoke the server's ``wsgi_application``.
- """
-
- self.wsgi_setup(environ)
-
- try:
- result = self.server.wsgi_application(self.wsgi_environ,
- self.wsgi_start_response)
- try:
- for chunk in result:
- self.wsgi_write_chunk(chunk)
- if not self.wsgi_headers_sent:
- self.wsgi_write_chunk('')
- finally:
- if hasattr(result,'close'):
- result.close()
- result = None
- except socket.error, exce:
- self.wsgi_connection_drop(exce, environ)
- return
- except:
- if not self.wsgi_headers_sent:
- error_msg = "Internal Server Error\n"
- self.wsgi_curr_headers = (
- '500 Internal Server Error',
- [('Content-type', 'text/plain'),
- ('Content-length', str(len(error_msg)))])
- self.wsgi_write_chunk("Internal Server Error\n")
- raise
-
-#
-# SSL Functionality
-#
-# This implementation was motivated by Sebastien Martini's SSL example
-# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
-#
-try:
- from OpenSSL import SSL, tsafe
- SocketErrors = (socket.error, SSL.ZeroReturnError, SSL.SysCallError)
-except ImportError:
- # Do not require pyOpenSSL to be installed, but disable SSL
- # functionality in that case.
- SSL = None
- SocketErrors = (socket.error,)
- class SecureHTTPServer(HTTPServer):
- def __init__(self, server_address, RequestHandlerClass,
- ssl_context=None, request_queue_size=None):
- assert not ssl_context, "pyOpenSSL not installed"
- HTTPServer.__init__(self, server_address, RequestHandlerClass)
- if request_queue_size:
- self.socket.listen(request_queue_size)
-else:
-
- class _ConnFixer(object):
- """ wraps a socket connection so it implements makefile """
- def __init__(self, conn):
- self.__conn = conn
- def makefile(self, mode, bufsize):
- return socket._fileobject(self.__conn, mode, bufsize)
- def __getattr__(self, attrib):
- return getattr(self.__conn, attrib)
-
- class SecureHTTPServer(HTTPServer):
- """
- Provides SSL server functionality on top of the BaseHTTPServer
- by overriding _private_ members of Python's standard
- distribution. The interface for this instance only changes by
- adding a an optional ssl_context attribute to the constructor:
-
- cntx = SSL.Context(SSL.SSLv23_METHOD)
- cntx.use_privatekey_file("host.pem")
- cntx.use_certificate_file("host.pem")
-
- """
-
- def __init__(self, server_address, RequestHandlerClass,
- ssl_context=None, request_queue_size=None):
- # This overrides the implementation of __init__ in python's
- # SocketServer.TCPServer (which BaseHTTPServer.HTTPServer
- # does not override, thankfully).
- HTTPServer.__init__(self, server_address, RequestHandlerClass)
- self.socket = socket.socket(self.address_family,
- self.socket_type)
- self.ssl_context = ssl_context
- if ssl_context:
- class TSafeConnection(tsafe.Connection):
- def settimeout(self, *args):
- self._lock.acquire()
- try:
- return self._ssl_conn.settimeout(*args)
- finally:
- self._lock.release()
- self.socket = TSafeConnection(ssl_context, self.socket)
- self.server_bind()
- if request_queue_size:
- self.socket.listen(request_queue_size)
- self.server_activate()
-
- def get_request(self):
- # The default SSL request object does not seem to have a
- # ``makefile(mode, bufsize)`` method as expected by
- # Socketserver.StreamRequestHandler.
- (conn, info) = self.socket.accept()
- if self.ssl_context:
- conn = _ConnFixer(conn)
- return (conn, info)
-
- def _auto_ssl_context():
- import OpenSSL, time, random
- pkey = OpenSSL.crypto.PKey()
- pkey.generate_key(OpenSSL.crypto.TYPE_RSA, 768)
-
- cert = OpenSSL.crypto.X509()
-
- cert.set_serial_number(random.randint(0, sys.maxint))
- cert.gmtime_adj_notBefore(0)
- cert.gmtime_adj_notAfter(60 * 60 * 24 * 365)
- cert.get_subject().CN = '*'
- cert.get_subject().O = 'Dummy Certificate'
- cert.get_issuer().CN = 'Untrusted Authority'
- cert.get_issuer().O = 'Self-Signed'
- cert.set_pubkey(pkey)
- cert.sign(pkey, 'md5')
-
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- ctx.use_privatekey(pkey)
- ctx.use_certificate(cert)
-
- return ctx
-
-class WSGIHandler(WSGIHandlerMixin, BaseHTTPRequestHandler):
- """
- A WSGI handler that overrides POST, GET and HEAD to delegate
- requests to the server's ``wsgi_application``.
- """
- server_version = 'PasteWSGIServer/' + __version__
-
- def handle_one_request(self):
- """Handle a single HTTP request.
-
- You normally don't need to override this method; see the class
- __doc__ string for information on how to handle specific HTTP
- commands such as GET and POST.
-
- """
- self.raw_requestline = self.rfile.readline()
- if not self.raw_requestline:
- self.close_connection = 1
- return
- if not self.parse_request(): # An error code has been sent, just exit
- return
- self.wsgi_execute()
-
- def handle(self):
- # don't bother logging disconnects while handling a request
- try:
- BaseHTTPRequestHandler.handle(self)
- except SocketErrors, exce:
- self.wsgi_connection_drop(exce)
-
- def address_string(self):
- """Return the client address formatted for logging.
-
- This is overridden so that no hostname lookup is done.
- """
- return ''
-
-class LimitedLengthFile(object):
- def __init__(self, file, length):
- self.file = file
- self.length = length
- self._consumed = 0
- if hasattr(self.file, 'seek'):
- self.seek = self._seek
-
- def __repr__(self):
- base_repr = repr(self.file)
- return base_repr[:-1] + ' length=%s>' % self.length
-
- def read(self, length=None):
- left = self.length - self._consumed
- if length is None:
- length = left
- else:
- length = min(length, left)
- # next two lines are hnecessary only if read(0) blocks
- if not left:
- return ''
- data = self.file.read(length)
- self._consumed += len(data)
- return data
-
- def readline(self, *args):
- max_read = self.length - self._consumed
- if len(args):
- max_read = min(args[0], max_read)
- data = self.file.readline(max_read)
- self._consumed += len(data)
- return data
-
- def readlines(self, hint=None):
- data = self.file.readlines(hint)
- for chunk in data:
- self._consumed += len(chunk)
- return data
-
- def __iter__(self):
- return self
-
- def next(self):
- if self.length - self._consumed <= 0:
- raise StopIteration
- return self.readline()
-
- ## Optional methods ##
-
- def _seek(self, place):
- self.file.seek(place)
- self._consumed = place
-
- def tell(self):
- if hasattr(self.file, 'tell'):
- return self.file.tell()
- else:
- return self._consumed
-
-class ThreadPool(object):
- """
- Generic thread pool with a queue of callables to consume.
-
- Keeps a notion of the status of its worker threads:
-
- idle: worker thread with nothing to do
-
- busy: worker thread doing its job
-
- hung: worker thread that's been doing a job for too long
-
- dying: a hung thread that has been killed, but hasn't died quite
- yet.
-
- zombie: what was a worker thread that we've tried to kill but
- isn't dead yet.
-
- At any time you can call track_threads, to get a dictionary with
- these keys and lists of thread_ids that fall in that status. All
- keys will be present, even if they point to emty lists.
-
- hung threads are threads that have been busy more than
- hung_thread_limit seconds. Hung threads are killed when they live
- longer than kill_thread_limit seconds. A thread is then
- considered dying for dying_limit seconds, if it is still alive
- after that it is considered a zombie.
-
- When there are no idle workers and a request comes in, another
- worker *may* be spawned. If there are less than spawn_if_under
- threads in the busy state, another thread will be spawned. So if
- the limit is 5, and there are 4 hung threads and 6 busy threads,
- no thread will be spawned.
-
- When there are more than max_zombie_threads_before_die zombie
- threads, a SystemExit exception will be raised, stopping the
- server. Use 0 or None to never raise this exception. Zombie
- threads *should* get cleaned up, but killing threads is no
- necessarily reliable. This is turned off by default, since it is
- only a good idea if you've deployed the server with some process
- watching from above (something similar to daemontools or zdaemon).
-
- Each worker thread only processes ``max_requests`` tasks before it
- dies and replaces itself with a new worker thread.
- """
-
-
- SHUTDOWN = object()
-
- def __init__(
- self, nworkers, name="ThreadPool", daemon=False,
- max_requests=100, # threads are killed after this many requests
- hung_thread_limit=30, # when a thread is marked "hung"
- kill_thread_limit=1800, # when you kill that hung thread
- dying_limit=300, # seconds that a kill should take to go into effect (longer than this and the thread is a "zombie")
- spawn_if_under=5, # spawn if there's too many hung threads
- max_zombie_threads_before_die=0, # when to give up on the process
- hung_check_period=100, # every 100 requests check for hung workers
- logger=None, # Place to log messages to
- error_email=None, # Person(s) to notify if serious problem occurs
- ):
- """
- Create thread pool with `nworkers` worker threads.
- """
- self.nworkers = nworkers
- self.max_requests = max_requests
- self.name = name
- self.queue = Queue.Queue()
- self.workers = []
- self.daemon = daemon
- if logger is None:
- logger = logging.getLogger('paste.httpserver.ThreadPool')
- if isinstance(logger, basestring):
- logger = logging.getLogger(logger)
- self.logger = logger
- self.error_email = error_email
- self._worker_count = count()
-
- assert (not kill_thread_limit
- or kill_thread_limit >= hung_thread_limit), (
- "kill_thread_limit (%s) should be higher than hung_thread_limit (%s)"
- % (kill_thread_limit, hung_thread_limit))
- if not killthread:
- kill_thread_limit = 0
- self.logger.info(
- "Cannot use kill_thread_limit as ctypes/killthread is not available")
- self.kill_thread_limit = kill_thread_limit
- self.dying_limit = dying_limit
- self.hung_thread_limit = hung_thread_limit
- assert spawn_if_under <= nworkers, (
- "spawn_if_under (%s) should be less than nworkers (%s)"
- % (spawn_if_under, nworkers))
- self.spawn_if_under = spawn_if_under
- self.max_zombie_threads_before_die = max_zombie_threads_before_die
- self.hung_check_period = hung_check_period
- self.requests_since_last_hung_check = 0
- # Used to keep track of what worker is doing what:
- self.worker_tracker = {}
- # Used to keep track of the workers not doing anything:
- self.idle_workers = []
- # Used to keep track of threads that have been killed, but maybe aren't dead yet:
- self.dying_threads = {}
- # This is used to track when we last had to add idle workers;
- # we shouldn't cull extra workers until some time has passed
- # (hung_thread_limit) since workers were added:
- self._last_added_new_idle_workers = 0
- if not daemon:
- atexit.register(self.shutdown)
- for i in range(self.nworkers):
- self.add_worker_thread(message='Initial worker pool')
-
- def add_task(self, task):
- """
- Add a task to the queue
- """
- self.logger.debug('Added task (%i tasks queued)', self.queue.qsize())
- if self.hung_check_period:
- self.requests_since_last_hung_check += 1
- if self.requests_since_last_hung_check > self.hung_check_period:
- self.requests_since_last_hung_check = 0
- self.kill_hung_threads()
- if not self.idle_workers and self.spawn_if_under:
- # spawn_if_under can come into effect...
- busy = 0
- now = time.time()
- self.logger.debug('No idle workers for task; checking if we need to make more workers')
- for worker in self.workers:
- if not hasattr(worker, 'thread_id'):
- # Not initialized
- continue
- time_started, info = self.worker_tracker.get(worker.thread_id,
- (None, None))
- if time_started is not None:
- if now - time_started < self.hung_thread_limit:
- busy += 1
- if busy < self.spawn_if_under:
- self.logger.info(
- 'No idle tasks, and only %s busy tasks; adding %s more '
- 'workers', busy, self.spawn_if_under-busy)
- self._last_added_new_idle_workers = time.time()
- for i in range(self.spawn_if_under - busy):
- self.add_worker_thread(message='Response to lack of idle workers')
- else:
- self.logger.debug(
- 'No extra workers needed (%s busy workers)',
- busy)
- if (len(self.workers) > self.nworkers
- and len(self.idle_workers) > 3
- and time.time()-self._last_added_new_idle_workers > self.hung_thread_limit):
- # We've spawned worers in the past, but they aren't needed
- # anymore; kill off some
- self.logger.info(
- 'Culling %s extra workers (%s idle workers present)',
- len(self.workers)-self.nworkers, len(self.idle_workers))
- self.logger.debug(
- 'Idle workers: %s', self.idle_workers)
- for i in range(len(self.workers) - self.nworkers):
- self.queue.put(self.SHUTDOWN)
- self.queue.put(task)
-
- def track_threads(self):
- """
- Return a dict summarizing the threads in the pool (as
- described in the ThreadPool docstring).
- """
- result = dict(idle=[], busy=[], hung=[], dying=[], zombie=[])
- now = time.time()
- for worker in self.workers:
- if not hasattr(worker, 'thread_id'):
- # The worker hasn't fully started up, we should just
- # ignore it
- continue
- time_started, info = self.worker_tracker.get(worker.thread_id,
- (None, None))
- if time_started is not None:
- if now - time_started > self.hung_thread_limit:
- result['hung'].append(worker)
- else:
- result['busy'].append(worker)
- else:
- result['idle'].append(worker)
- for thread_id, (time_killed, worker) in self.dying_threads.items():
- if not self.thread_exists(thread_id):
- # Cull dying threads that are actually dead and gone
- self.logger.info('Killed thread %s no longer around',
- thread_id)
- try:
- del self.dying_threads[thread_id]
- except KeyError:
- pass
- continue
- if now - time_killed > self.dying_limit:
- result['zombie'].append(worker)
- else:
- result['dying'].append(worker)
- return result
-
- def kill_worker(self, thread_id):
- """
- Removes the worker with the given thread_id from the pool, and
- replaces it with a new worker thread.
-
- This should only be done for mis-behaving workers.
- """
- if killthread is None:
- raise RuntimeError(
- "Cannot kill worker; killthread/ctypes not available")
- thread_obj = threading._active.get(thread_id)
- killthread.async_raise(thread_id, SystemExit)
- try:
- del self.worker_tracker[thread_id]
- except KeyError:
- pass
- self.logger.info('Killing thread %s', thread_id)
- if thread_obj in self.workers:
- self.workers.remove(thread_obj)
- self.dying_threads[thread_id] = (time.time(), thread_obj)
- self.add_worker_thread(message='Replacement for killed thread %s' % thread_id)
-
- def thread_exists(self, thread_id):
- """
- Returns true if a thread with this id is still running
- """
- return thread_id in threading._active
-
- def add_worker_thread(self, *args, **kwargs):
- index = self._worker_count.next()
- worker = threading.Thread(target=self.worker_thread_callback,
- args=args, kwargs=kwargs,
- name=("worker %d" % index))
- worker.setDaemon(self.daemon)
- worker.start()
-
- def kill_hung_threads(self):
- """
- Tries to kill any hung threads
- """
- if not self.kill_thread_limit:
- # No killing should occur
- return
- now = time.time()
- max_time = 0
- total_time = 0
- idle_workers = 0
- starting_workers = 0
- working_workers = 0
- killed_workers = 0
- for worker in self.workers:
- if not hasattr(worker, 'thread_id'):
- # Not setup yet
- starting_workers += 1
- continue
- time_started, info = self.worker_tracker.get(worker.thread_id,
- (None, None))
- if time_started is None:
- # Must be idle
- idle_workers += 1
- continue
- working_workers += 1
- max_time = max(max_time, now-time_started)
- total_time += now-time_started
- if now - time_started > self.kill_thread_limit:
- self.logger.warning(
- 'Thread %s hung (working on task for %i seconds)',
- worker.thread_id, now - time_started)
- try:
- import pprint
- info_desc = pprint.pformat(info)
- except:
- out = StringIO()
- traceback.print_exc(file=out)
- info_desc = 'Error:\n%s' % out.getvalue()
- self.notify_problem(
- "Killing worker thread (id=%(thread_id)s) because it has been \n"
- "working on task for %(time)s seconds (limit is %(limit)s)\n"
- "Info on task:\n"
- "%(info)s"
- % dict(thread_id=worker.thread_id,
- time=now - time_started,
- limit=self.kill_thread_limit,
- info=info_desc))
- self.kill_worker(worker.thread_id)
- killed_workers += 1
- if working_workers:
- ave_time = float(total_time) / working_workers
- ave_time = '%.2fsec' % ave_time
- else:
- ave_time = 'N/A'
- self.logger.info(
- "kill_hung_threads status: %s threads (%s working, %s idle, %s starting) "
- "ave time %s, max time %.2fsec, killed %s workers"
- % (idle_workers + starting_workers + working_workers,
- working_workers, idle_workers, starting_workers,
- ave_time, max_time, killed_workers))
- self.check_max_zombies()
-
- def check_max_zombies(self):
- """
- Check if we've reached max_zombie_threads_before_die; if so
- then kill the entire process.
- """
- if not self.max_zombie_threads_before_die:
- return
- found = []
- now = time.time()
- for thread_id, (time_killed, worker) in self.dying_threads.items():
- if not self.thread_exists(thread_id):
- # Cull dying threads that are actually dead and gone
- try:
- del self.dying_threads[thread_id]
- except KeyError:
- pass
- continue
- if now - time_killed > self.dying_limit:
- found.append(thread_id)
- if found:
- self.logger.info('Found %s zombie threads', found)
- if len(found) > self.max_zombie_threads_before_die:
- self.logger.fatal(
- 'Exiting process because %s zombie threads is more than %s limit',
- len(found), self.max_zombie_threads_before_die)
- self.notify_problem(
- "Exiting process because %(found)s zombie threads "
- "(more than limit of %(limit)s)\n"
- "Bad threads (ids):\n"
- " %(ids)s\n"
- % dict(found=len(found),
- limit=self.max_zombie_threads_before_die,
- ids="\n ".join(map(str, found))),
- subject="Process restart (too many zombie threads)")
- self.shutdown(10)
- print 'Shutting down', threading.currentThread()
- raise ServerExit(3)
-
- def worker_thread_callback(self, message=None):
- """
- Worker thread should call this method to get and process queued
- callables.
- """
- thread_obj = threading.currentThread()
- thread_id = thread_obj.thread_id = thread.get_ident()
- self.workers.append(thread_obj)
- self.idle_workers.append(thread_id)
- requests_processed = 0
- add_replacement_worker = False
- self.logger.debug('Started new worker %s: %s', thread_id, message)
- try:
- while True:
- if self.max_requests and self.max_requests < requests_processed:
- # Replace this thread then die
- self.logger.debug('Thread %s processed %i requests (limit %s); stopping thread'
- % (thread_id, requests_processed, self.max_requests))
- add_replacement_worker = True
- break
- runnable = self.queue.get()
- if runnable is ThreadPool.SHUTDOWN:
- self.logger.debug('Worker %s asked to SHUTDOWN', thread_id)
- break
- try:
- self.idle_workers.remove(thread_id)
- except ValueError:
- pass
- self.worker_tracker[thread_id] = [time.time(), None]
- requests_processed += 1
- try:
- try:
- runnable()
- except:
- # We are later going to call sys.exc_clear(),
- # removing all remnants of any exception, so
- # we should log it now. But ideally no
- # exception should reach this level
- print >> sys.stderr, (
- 'Unexpected exception in worker %r' % runnable)
- traceback.print_exc()
- if thread_id in self.dying_threads:
- # That last exception was intended to kill me
- break
- finally:
- try:
- del self.worker_tracker[thread_id]
- except KeyError:
- pass
- sys.exc_clear()
- self.idle_workers.append(thread_id)
- finally:
- try:
- del self.worker_tracker[thread_id]
- except KeyError:
- pass
- try:
- self.idle_workers.remove(thread_id)
- except ValueError:
- pass
- try:
- self.workers.remove(thread_obj)
- except ValueError:
- pass
- try:
- del self.dying_threads[thread_id]
- except KeyError:
- pass
- if add_replacement_worker:
- self.add_worker_thread(message='Voluntary replacement for thread %s' % thread_id)
-
- def shutdown(self, force_quit_timeout=0):
- """
- Shutdown the queue (after finishing any pending requests).
- """
- self.logger.info('Shutting down threadpool')
- # Add a shutdown request for every worker
- for i in range(len(self.workers)):
- self.queue.put(ThreadPool.SHUTDOWN)
- # Wait for each thread to terminate
- hung_workers = []
- for worker in self.workers:
- worker.join(0.5)
- if worker.isAlive():
- hung_workers.append(worker)
- zombies = []
- for thread_id in self.dying_threads:
- if self.thread_exists(thread_id):
- zombies.append(thread_id)
- if hung_workers or zombies:
- self.logger.info("%s workers didn't stop properly, and %s zombies",
- len(hung_workers), len(zombies))
- if hung_workers:
- for worker in hung_workers:
- 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 workers in self.workers:
- if not timed_out and worker.isAlive():
- timed_out = True
- worker.join(force_quit_timeout)
- if worker.isAlive():
- print "Worker %s won't die" % worker
- need_force_quit = True
- if need_force_quit:
- import atexit
- # Remove the threading atexit callback
- for callback in list(atexit._exithandlers):
- func = getattr(callback[0], 'im_func', None)
- if not func:
- continue
- globs = getattr(func, 'func_globals', {})
- mod = globs.get('__name__')
- if mod == 'threading':
- atexit._exithandlers.remove(callback)
- atexit._run_exitfuncs()
- print 'Forcefully exiting process'
- os._exit(3)
- else:
- self.logger.info('All workers eventually killed')
- else:
- self.logger.info('All workers stopped')
-
- def notify_problem(self, msg, subject=None, spawn_thread=True):
- """
- Called when there's a substantial problem. msg contains the
- body of the notification, subject the summary.
-
- If spawn_thread is true, then the email will be send in
- another thread (so this doesn't block).
- """
- if not self.error_email:
- return
- if spawn_thread:
- t = threading.Thread(
- target=self.notify_problem,
- args=(msg, subject, False))
- t.start()
- return
- from_address = 'errors@localhost'
- if not subject:
- subject = msg.strip().splitlines()[0]
- subject = subject[:50]
- subject = '[http threadpool] %s' % subject
- headers = [
- "To: %s" % self.error_email,
- "From: %s" % from_address,
- "Subject: %s" % subject,
- ]
- try:
- system = ' '.join(os.uname())
- except:
- system = '(unknown)'
- body = (
- "An error has occurred in the paste.httpserver.ThreadPool\n"
- "Error:\n"
- " %(msg)s\n"
- "Occurred at: %(time)s\n"
- "PID: %(pid)s\n"
- "System: %(system)s\n"
- "Server .py file: %(file)s\n"
- % dict(msg=msg,
- time=time.strftime("%c"),
- pid=os.getpid(),
- system=system,
- file=os.path.abspath(__file__),
- ))
- message = '\n'.join(headers) + "\n\n" + body
- import smtplib
- server = smtplib.SMTP('localhost')
- error_emails = [
- e.strip() for e in self.error_email.split(",")
- if e.strip()]
- server.sendmail(from_address, error_emails, message)
- server.quit()
- print 'email sent to', error_emails, message
-
-class ThreadPoolMixIn(object):
- """
- Mix-in class to process requests from a thread pool
- """
- def __init__(self, nworkers, daemon=False, **threadpool_options):
- # Create and start the workers
- self.running = True
- assert nworkers > 0, "ThreadPoolMixIn servers must have at least one worker"
- self.thread_pool = ThreadPool(
- nworkers,
- "ThreadPoolMixIn HTTP server on %s:%d"
- % (self.server_name, self.server_port),
- daemon,
- **threadpool_options)
-
- def process_request(self, request, client_address):
- """
- Queue the request to be processed by on of the thread pool threads
- """
- # This sets the socket to blocking mode (and no timeout) since it
- # may take the thread pool a little while to get back to it. (This
- # is the default but since we set a timeout on the parent socket so
- # that we can trap interrupts we need to restore this,.)
- request.setblocking(1)
- # Queue processing of the request
- self.thread_pool.add_task(
- lambda: self.process_request_in_thread(request, client_address))
-
- def handle_error(self, request, client_address):
- exc_class, exc, tb = sys.exc_info()
- if exc_class is ServerExit:
- # This is actually a request to stop the server
- raise
- return super(ThreadPoolMixIn, self).handle_error(request, client_address)
-
- def process_request_in_thread(self, request, client_address):
- """
- The worker thread should call back here to do the rest of the
- request processing. Error handling normaller done in 'handle_request'
- must be done here.
- """
- try:
- self.finish_request(request, client_address)
- self.close_request(request)
- except:
- self.handle_error(request, client_address)
- self.close_request(request)
- exc = sys.exc_info()[1]
- if isinstance(exc, (MemoryError, KeyboardInterrupt)):
- raise
-
- def serve_forever(self):
- """
- Overrides `serve_forever` to shut the threadpool down cleanly.
- """
- try:
- while self.running:
- try:
- self.handle_request()
- except socket.timeout:
- # Timeout is expected, gives interrupts a chance to
- # propogate, just keep handling
- pass
- finally:
- self.thread_pool.shutdown()
-
- def server_activate(self):
- """
- Overrides server_activate to set timeout on our listener socket.
- """
- # We set the timeout here so that we can trap interrupts on windows
- self.socket.settimeout(1)
-
- def server_close(self):
- """
- Finish pending requests and shutdown the server.
- """
- self.running = False
- self.socket.close()
- self.thread_pool.shutdown(60)
-
-class WSGIServerBase(SecureHTTPServer):
- def __init__(self, wsgi_application, server_address,
- RequestHandlerClass=None, ssl_context=None,
- request_queue_size=None):
- SecureHTTPServer.__init__(self, server_address,
- RequestHandlerClass, ssl_context,
- request_queue_size=request_queue_size)
- self.wsgi_application = wsgi_application
- self.wsgi_socket_timeout = None
-
- def get_request(self):
- # If there is a socket_timeout, set it on the accepted
- (conn,info) = SecureHTTPServer.get_request(self)
- if self.wsgi_socket_timeout:
- conn.settimeout(self.wsgi_socket_timeout)
- return (conn, info)
-
-class WSGIServer(ThreadingMixIn, WSGIServerBase):
- daemon_threads = False
-
-class WSGIThreadPoolServer(ThreadPoolMixIn, WSGIServerBase):
- def __init__(self, wsgi_application, server_address,
- RequestHandlerClass=None, ssl_context=None,
- nworkers=10, daemon_threads=False,
- threadpool_options=None, request_queue_size=None):
- WSGIServerBase.__init__(self, wsgi_application, server_address,
- RequestHandlerClass, ssl_context,
- request_queue_size=request_queue_size)
- if threadpool_options is None:
- threadpool_options = {}
- ThreadPoolMixIn.__init__(self, nworkers, daemon_threads,
- **threadpool_options)
-
-class ServerExit(SystemExit):
- """
- Raised to tell the server to really exit (SystemExit is normally
- caught)
- """
-
-def serve(application, host=None, port=None, handler=None, ssl_pem=None,
- ssl_context=None, server_version=None, protocol_version=None,
- start_loop=True, daemon_threads=None, socket_timeout=None,
- use_threadpool=None, threadpool_workers=10,
- threadpool_options=None, request_queue_size=5):
- """
- Serves your ``application`` over HTTP(S) via WSGI interface
-
- ``host``
-
- This is the ipaddress to bind to (or a hostname if your
- nameserver is properly configured). This defaults to
- 127.0.0.1, which is not a public interface.
-
- ``port``
-
- The port to run on, defaults to 8080 for HTTP, or 4443 for
- HTTPS. This can be a string or an integer value.
-
- ``handler``
-
- This is the HTTP request handler to use, it defaults to
- ``WSGIHandler`` in this module.
-
- ``ssl_pem``
-
- This an optional SSL certificate file (via OpenSSL). You can
- supply ``*`` and a development-only certificate will be
- created for you, or you can generate a self-signed test PEM
- certificate file as follows::
-
- $ openssl genrsa 1024 > host.key
- $ chmod 400 host.key
- $ openssl req -new -x509 -nodes -sha1 -days 365 \\
- -key host.key > host.cert
- $ cat host.cert host.key > host.pem
- $ chmod 400 host.pem
-
- ``ssl_context``
-
- This an optional SSL context object for the server. A SSL
- context will be automatically constructed for you if you supply
- ``ssl_pem``. Supply this to use a context of your own
- construction.
-
- ``server_version``
-
- The version of the server as reported in HTTP response line. This
- defaults to something like "PasteWSGIServer/0.5". Many servers
- hide their code-base identity with a name like 'Amnesiac/1.0'
-
- ``protocol_version``
-
- This sets the protocol used by the server, by default
- ``HTTP/1.0``. There is some support for ``HTTP/1.1``, which
- defaults to nicer keep-alive connections. This server supports
- ``100 Continue``, but does not yet support HTTP/1.1 Chunked
- Encoding. Hence, if you use HTTP/1.1, you're somewhat in error
- since chunked coding is a mandatory requirement of a HTTP/1.1
- server. If you specify HTTP/1.1, every response *must* have a
- ``Content-Length`` and you must be careful not to read past the
- end of the socket.
-
- ``start_loop``
-
- This specifies if the server loop (aka ``server.serve_forever()``)
- should be called; it defaults to ``True``.
-
- ``daemon_threads``
-
- This flag specifies if when your webserver terminates all
- in-progress client connections should be droppped. It defaults
- to ``False``. You might want to set this to ``True`` if you
- are using ``HTTP/1.1`` and don't set a ``socket_timeout``.
-
- ``socket_timeout``
-
- This specifies the maximum amount of time that a connection to a
- given client will be kept open. At this time, it is a rude
- disconnect, but at a later time it might follow the RFC a bit
- more closely.
-
- ``use_threadpool``
-
- Server requests from a pool of worker threads (``threadpool_workers``)
- rather than creating a new thread for each request. This can
- substantially reduce latency since there is a high cost associated
- with thread creation.
-
- ``threadpool_workers``
-
- Number of worker threads to create when ``use_threadpool`` is true. This
- can be a string or an integer value.
-
- ``threadpool_options``
-
- A dictionary of options to be used when instantiating the
- threadpool. See paste.httpserver.ThreadPool for specific
- options (``threadpool_workers`` is a specific option that can
- also go here).
-
- ``request_queue_size``
-
- The 'backlog' argument to socket.listen(); specifies the
- maximum number of queued connections.
-
- """
- is_ssl = False
- if ssl_pem or ssl_context:
- assert SSL, "pyOpenSSL is not installed"
- is_ssl = True
- port = int(port or 4443)
- if not ssl_context:
- if ssl_pem == '*':
- ssl_context = _auto_ssl_context()
- else:
- ssl_context = SSL.Context(SSL.SSLv23_METHOD)
- ssl_context.use_privatekey_file(ssl_pem)
- ssl_context.use_certificate_chain_file(ssl_pem)
-
- host = host or '127.0.0.1'
- if port is None:
- if ':' in host:
- host, port = host.split(':', 1)
- else:
- port = 8080
- server_address = (host, int(port))
-
- if not handler:
- handler = WSGIHandler
- if server_version:
- handler.server_version = server_version
- handler.sys_version = None
- if protocol_version:
- assert protocol_version in ('HTTP/0.9', 'HTTP/1.0', 'HTTP/1.1')
- handler.protocol_version = protocol_version
-
- if use_threadpool is None:
- use_threadpool = True
-
- if converters.asbool(use_threadpool):
- server = WSGIThreadPoolServer(application, server_address, handler,
- ssl_context, int(threadpool_workers),
- daemon_threads,
- threadpool_options=threadpool_options,
- request_queue_size=request_queue_size)
- else:
- server = WSGIServer(application, server_address, handler, ssl_context,
- request_queue_size=request_queue_size)
- if daemon_threads:
- server.daemon_threads = daemon_threads
-
- if socket_timeout:
- server.wsgi_socket_timeout = int(socket_timeout)
-
- if converters.asbool(start_loop):
- protocol = is_ssl and 'https' or 'http'
- host, port = server.server_address
- if host == '0.0.0.0':
- print 'serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' % \
- (port, protocol, port)
- else:
- print "serving on %s://%s:%s" % (protocol, host, port)
- try:
- server.serve_forever()
- except KeyboardInterrupt:
- # allow CTRL+C to shutdown
- pass
- return server
-
-# For paste.deploy server instantiation (egg:Paste#http)
-# Note: this gets a separate function because it has to expect string
-# arguments (though that's not much of an issue yet, ever?)
-def server_runner(wsgi_app, global_conf, **kwargs):
- from paste.deploy.converters import asbool
- for name in ['port', 'socket_timeout', 'threadpool_workers',
- 'threadpool_hung_thread_limit',
- 'threadpool_kill_thread_limit',
- 'threadpool_dying_limit', 'threadpool_spawn_if_under',
- 'threadpool_max_zombie_threads_before_die',
- 'threadpool_hung_check_period',
- 'threadpool_max_requests', 'request_queue_size']:
- if name in kwargs:
- kwargs[name] = int(kwargs[name])
- for name in ['use_threadpool', 'daemon_threads']:
- if name in kwargs:
- kwargs[name] = asbool(kwargs[name])
- threadpool_options = {}
- for name, value in kwargs.items():
- if name.startswith('threadpool_') and name != 'threadpool_workers':
- threadpool_options[name[len('threadpool_'):]] = value
- del kwargs[name]
- if ('error_email' not in threadpool_options
- and 'error_email' in global_conf):
- threadpool_options['error_email'] = global_conf['error_email']
- kwargs['threadpool_options'] = threadpool_options
- serve(wsgi_app, **kwargs)
-
-server_runner.__doc__ = (serve.__doc__ or '') + """
-
- You can also set these threadpool options:
-
- ``threadpool_max_requests``:
-
- The maximum number of requests a worker thread will process
- before dying (and replacing itself with a new worker thread).
- Default 100.
-
- ``threadpool_hung_thread_limit``:
-
- The number of seconds a thread can work on a task before it is
- considered hung (stuck). Default 30 seconds.
-
- ``threadpool_kill_thread_limit``:
-
- The number of seconds a thread can work before you should kill it
- (assuming it will never finish). Default 600 seconds (10 minutes).
-
- ``threadpool_dying_limit``:
-
- The length of time after killing a thread that it should actually
- disappear. If it lives longer than this, it is considered a
- "zombie". Note that even in easy situations killing a thread can
- be very slow. Default 300 seconds (5 minutes).
-
- ``threadpool_spawn_if_under``:
-
- If there are no idle threads and a request comes in, and there are
- less than this number of *busy* threads, then add workers to the
- pool. Busy threads are threads that have taken less than
- ``threadpool_hung_thread_limit`` seconds so far. So if you get
- *lots* of requests but they complete in a reasonable amount of time,
- the requests will simply queue up (adding more threads probably
- wouldn't speed them up). But if you have lots of hung threads and
- one more request comes in, this will add workers to handle it.
- Default 5.
-
- ``threadpool_max_zombie_threads_before_die``:
-
- If there are more zombies than this, just kill the process. This is
- only good if you have a monitor that will automatically restart
- the server. This can clean up the mess. Default 0 (disabled).
-
- `threadpool_hung_check_period``:
-
- Every X requests, check for hung threads that need to be killed,
- or for zombie threads that should cause a restart. Default 100
- requests.
-
- ``threadpool_logger``:
-
- Logging messages will go the logger named here.
-
- ``threadpool_error_email`` (or global ``error_email`` setting):
-
- When threads are killed or the process restarted, this email
- address will be contacted (using an SMTP server on localhost).
-
-"""
-
-
-if __name__ == '__main__':
- from paste.wsgilib import dump_environ
- #serve(dump_environ, ssl_pem="test.pem")
- serve(dump_environ, server_version="Wombles/1.0",
- protocol_version="HTTP/1.1", port="8888")
-
diff --git a/lib/paste/lint.py b/lib/paste/lint.py
@@ -1,436 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# Also licenced under the Apache License, 2.0: http://opensource.org/licenses/apache2.0.php
-# Licensed to PSF under a Contributor Agreement
-"""
-Middleware to check for obedience to the WSGI specification.
-
-Some of the things this checks:
-
-* Signature of the application and start_response (including that
- keyword arguments are not used).
-
-* Environment checks:
-
- - Environment is a dictionary (and not a subclass).
-
- - That all the required keys are in the environment: REQUEST_METHOD,
- SERVER_NAME, SERVER_PORT, wsgi.version, wsgi.input, wsgi.errors,
- wsgi.multithread, wsgi.multiprocess, wsgi.run_once
-
- - That HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH are not in the
- environment (these headers should appear as CONTENT_LENGTH and
- CONTENT_TYPE).
-
- - Warns if QUERY_STRING is missing, as the cgi module acts
- unpredictably in that case.
-
- - That CGI-style variables (that don't contain a .) have
- (non-unicode) string values
-
- - That wsgi.version is a tuple
-
- - That wsgi.url_scheme is 'http' or 'https' (@@: is this too
- restrictive?)
-
- - Warns if the REQUEST_METHOD is not known (@@: probably too
- restrictive).
-
- - That SCRIPT_NAME and PATH_INFO are empty or start with /
-
- - That at least one of SCRIPT_NAME or PATH_INFO are set.
-
- - That CONTENT_LENGTH is a positive integer.
-
- - That SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should
- be '/').
-
- - That wsgi.input has the methods read, readline, readlines, and
- __iter__
-
- - That wsgi.errors has the methods flush, write, writelines
-
-* The status is a string, contains a space, starts with an integer,
- and that integer is in range (> 100).
-
-* That the headers is a list (not a subclass, not another kind of
- sequence).
-
-* That the items of the headers are tuples of strings.
-
-* That there is no 'status' header (that is used in CGI, but not in
- WSGI).
-
-* That the headers don't contain newlines or colons, end in _ or -, or
- contain characters codes below 037.
-
-* That Content-Type is given if there is content (CGI often has a
- default content type, but WSGI does not).
-
-* That no Content-Type is given when there is no content (@@: is this
- too restrictive?)
-
-* That the exc_info argument to start_response is a tuple or None.
-
-* That all calls to the writer are with strings, and no other methods
- on the writer are accessed.
-
-* That wsgi.input is used properly:
-
- - .read() is called with zero or one argument
-
- - That it returns a string
-
- - That readline, readlines, and __iter__ return strings
-
- - That .close() is not called
-
- - No other methods are provided
-
-* That wsgi.errors is used properly:
-
- - .write() and .writelines() is called with a string
-
- - That .close() is not called, and no other methods are provided.
-
-* The response iterator:
-
- - That it is not a string (it should be a list of a single string; a
- string will work, but perform horribly).
-
- - That .next() returns a string
-
- - That the iterator is not iterated over until start_response has
- been called (that can signal either a server or application
- error).
-
- - That .close() is called (doesn't raise exception, only prints to
- sys.stderr, because we only know it isn't called when the object
- is garbage collected).
-"""
-
-import re
-import sys
-from types import DictType, StringType, TupleType, ListType
-import warnings
-
-header_re = re.compile(r'^[a-zA-Z][a-zA-Z0-9\-_]*$')
-bad_header_value_re = re.compile(r'[\000-\037]')
-
-class WSGIWarning(Warning):
- """
- Raised in response to WSGI-spec-related warnings
- """
-
-def middleware(application, global_conf=None):
-
- """
- When applied between a WSGI server and a WSGI application, this
- middleware will check for WSGI compliancy on a number of levels.
- This middleware does not modify the request or response in any
- way, but will throw an AssertionError if anything seems off
- (except for a failure to close the application iterator, which
- 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"
- environ, start_response = args
-
- check_environ(environ)
-
- # We use this to check if the application returns without
- # calling start_response:
- start_response_started = []
-
- def start_response_wrapper(*args, **kw):
- assert len(args) == 2 or len(args) == 3, (
- "Invalid number of arguments: %s" % args)
- assert not kw, "No keyword arguments allowed"
- status = args[0]
- headers = args[1]
- if len(args) == 3:
- exc_info = args[2]
- else:
- exc_info = None
-
- check_status(status)
- check_headers(headers)
- check_content_type(status, headers)
- check_exc_info(exc_info)
-
- start_response_started.append(None)
- return WriteWrapper(start_response(*args))
-
- environ['wsgi.input'] = InputWrapper(environ['wsgi.input'])
- environ['wsgi.errors'] = ErrorWrapper(environ['wsgi.errors'])
-
- iterator = application(environ, start_response_wrapper)
- assert iterator is not None and iterator != False, (
- "The application must return an iterator, if only an empty list")
-
- check_iterator(iterator)
-
- return IteratorWrapper(iterator, start_response_started)
-
- return lint_app
-
-class InputWrapper(object):
-
- def __init__(self, wsgi_input):
- self.input = wsgi_input
-
- def read(self, *args):
- assert len(args) <= 1
- v = self.input.read(*args)
- assert type(v) is type("")
- return v
-
- def readline(self, *args):
- v = self.input.readline(*args)
- assert type(v) is type("")
- return v
-
- def readlines(self, *args):
- assert len(args) <= 1
- lines = self.input.readlines(*args)
- assert type(lines) is type([])
- for line in lines:
- assert type(line) is type("")
- return lines
-
- def __iter__(self):
- while 1:
- line = self.readline()
- if not line:
- return
- yield line
-
- def close(self):
- assert 0, "input.close() must not be called"
-
-class ErrorWrapper(object):
-
- def __init__(self, wsgi_errors):
- self.errors = wsgi_errors
-
- def write(self, s):
- assert type(s) is type("")
- self.errors.write(s)
-
- def flush(self):
- self.errors.flush()
-
- def writelines(self, seq):
- for line in seq:
- self.write(line)
-
- def close(self):
- assert 0, "errors.close() must not be called"
-
-class WriteWrapper(object):
-
- def __init__(self, wsgi_writer):
- self.writer = wsgi_writer
-
- def __call__(self, s):
- assert type(s) is type("")
- self.writer(s)
-
-class PartialIteratorWrapper(object):
-
- def __init__(self, wsgi_iterator):
- self.iterator = wsgi_iterator
-
- def __iter__(self):
- # We want to make sure __iter__ is called
- return IteratorWrapper(self.iterator)
-
-class IteratorWrapper(object):
-
- def __init__(self, wsgi_iterator, check_start_response):
- self.original_iterator = wsgi_iterator
- self.iterator = iter(wsgi_iterator)
- self.closed = False
- self.check_start_response = check_start_response
-
- def __iter__(self):
- return self
-
- def next(self):
- assert not self.closed, (
- "Iterator read after closed")
- v = self.iterator.next()
- if self.check_start_response is not None:
- assert self.check_start_response, (
- "The application returns and we started iterating over its body, but start_response has not yet been called")
- self.check_start_response = None
- return v
-
- def close(self):
- self.closed = True
- if hasattr(self.original_iterator, 'close'):
- self.original_iterator.close()
-
- def __del__(self):
- if not self.closed:
- sys.stderr.write(
- "Iterator garbage collected without being closed")
- assert self.closed, (
- "Iterator garbage collected without being closed")
-
-def check_environ(environ):
- assert type(environ) is DictType, (
- "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',
- 'wsgi.run_once']:
- assert key in environ, (
- "Environment missing required key: %r" % key)
-
- for key in ['HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH']:
- assert key not in environ, (
- "Environment should not have the key: %s "
- "(use %s instead)" % (key, key[5:]))
-
- if 'QUERY_STRING' not in environ:
- warnings.warn(
- 'QUERY_STRING is not in the WSGI environment; the cgi '
- 'module will use sys.argv when this variable is missing, '
- 'so application errors are more likely',
- WSGIWarning)
-
- for key in environ.keys():
- if '.' in key:
- # Extension, we don't care about its type
- continue
- assert type(environ[key]) is StringType, (
- "Environmental variable %s is not a string: %r (value: %r)"
- % (key, type(environ[key]), environ[key]))
-
- assert type(environ['wsgi.version']) is TupleType, (
- "wsgi.version should be a tuple (%r)" % environ['wsgi.version'])
- assert environ['wsgi.url_scheme'] in ('http', 'https'), (
- "wsgi.url_scheme unknown: %r" % environ['wsgi.url_scheme'])
-
- check_input(environ['wsgi.input'])
- check_errors(environ['wsgi.errors'])
-
- # @@: these need filling out:
- if environ['REQUEST_METHOD'] not in (
- 'GET', 'HEAD', 'POST', 'OPTIONS','PUT','DELETE','TRACE'):
- warnings.warn(
- "Unknown REQUEST_METHOD: %r" % environ['REQUEST_METHOD'],
- WSGIWarning)
-
- assert (not environ.get('SCRIPT_NAME')
- or environ['SCRIPT_NAME'].startswith('/')), (
- "SCRIPT_NAME doesn't start with /: %r" % environ['SCRIPT_NAME'])
- assert (not environ.get('PATH_INFO')
- or environ['PATH_INFO'].startswith('/')), (
- "PATH_INFO doesn't start with /: %r" % environ['PATH_INFO'])
- if environ.get('CONTENT_LENGTH'):
- assert int(environ['CONTENT_LENGTH']) >= 0, (
- "Invalid CONTENT_LENGTH: %r" % environ['CONTENT_LENGTH'])
-
- if not environ.get('SCRIPT_NAME'):
- assert environ.has_key('PATH_INFO'), (
- "One of SCRIPT_NAME or PATH_INFO are required (PATH_INFO "
- "should at least be '/' if SCRIPT_NAME is empty)")
- assert environ.get('SCRIPT_NAME') != '/', (
- "SCRIPT_NAME cannot be '/'; it should instead be '', and "
- "PATH_INFO should be '/'")
-
-def check_input(wsgi_input):
- for attr in ['read', 'readline', 'readlines', '__iter__']:
- assert hasattr(wsgi_input, attr), (
- "wsgi.input (%r) doesn't have the attribute %s"
- % (wsgi_input, attr))
-
-def check_errors(wsgi_errors):
- for attr in ['flush', 'write', 'writelines']:
- assert hasattr(wsgi_errors, attr), (
- "wsgi.errors (%r) doesn't have the attribute %s"
- % (wsgi_errors, attr))
-
-def check_status(status):
- assert type(status) is StringType, (
- "Status must be a string (not %r)" % status)
- # Implicitly check that we can turn it into an integer:
- status_code = status.split(None, 1)[0]
- assert len(status_code) == 3, (
- "Status codes must be three characters: %r" % status_code)
- status_int = int(status_code)
- assert status_int >= 100, "Status code is invalid: %r" % status_int
- if len(status) < 4 or status[3] != ' ':
- warnings.warn(
- "The status string (%r) should be a three-digit integer "
- "followed by a single space and a status explanation"
- % status, WSGIWarning)
-
-def check_headers(headers):
- assert type(headers) is ListType, (
- "Headers (%r) must be of type list: %r"
- % (headers, type(headers)))
- header_names = {}
- for item in headers:
- assert type(item) is TupleType, (
- "Individual headers (%r) must be of type tuple: %r"
- % (item, type(item)))
- assert len(item) == 2
- name, value = item
- assert name.lower() != 'status', (
- "The Status header cannot be used; it conflicts with CGI "
- "script, and HTTP status is not given through headers "
- "(value: %r)." % value)
- header_names[name.lower()] = None
- assert '\n' not in name and ':' not in name, (
- "Header names may not contain ':' or '\\n': %r" % name)
- assert header_re.search(name), "Bad header name: %r" % name
- assert not name.endswith('-') and not name.endswith('_'), (
- "Names may not end in '-' or '_': %r" % name)
- assert not bad_header_value_re.search(value), (
- "Bad header value: %r (bad char: %r)"
- % (value, bad_header_value_re.search(value).group(0)))
-
-def check_content_type(status, headers):
- code = int(status.split(None, 1)[0])
- # @@: need one more person to verify this interpretation of RFC 2616
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- NO_MESSAGE_BODY = (201, 204, 304)
- NO_MESSAGE_TYPE = (204, 304)
- for name, value in headers:
- if name.lower() == 'content-type':
- if code not in NO_MESSAGE_TYPE:
- return
- assert 0, (("Content-Type header found in a %s response, "
- "which must not return content.") % code)
- if code not in NO_MESSAGE_BODY:
- assert 0, "No Content-Type header found in headers (%s)" % headers
-
-def check_exc_info(exc_info):
- assert exc_info is None or type(exc_info) is type(()), (
- "exc_info (%r) is not a tuple: %r" % (exc_info, type(exc_info)))
- # More exc_info checks?
-
-def check_iterator(iterator):
- # Technically a string is legal, which is why it's a really bad
- # idea, because it may cause the response to be returned
- # character-by-character
- assert not isinstance(iterator, str), (
- "You should not return a string as your application iterator, "
- "instead return a single-item list containing that string.")
-
-def make_middleware(application, global_conf):
- # @@: global_conf should be taken out of the middleware function,
- # and isolated here
- return middleware(application)
-
-make_middleware.__doc__ = __doc__
-
-__all__ = ['middleware', 'make_middleware']
diff --git a/lib/paste/modpython.py b/lib/paste/modpython.py
@@ -1,252 +0,0 @@
-"""WSGI Paste wrapper for mod_python. Requires Python 2.2 or greater.
-
-
-Example httpd.conf section for a Paste app with an ini file::
-
- <Location />
- SetHandler python-program
- 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``::
-
- <Location />
- SetHandler python-program
- PythonHandler paste.modpython
- PythonPath "['/virtual/project/directory'] + sys.path"
- PythonOption wsgi.application startup::app
- </Location>
-
-
-If you'd like to use a virtual installation, make sure to add it in the path
-like so::
-
- <Location />
- SetHandler python-program
- PythonHandler paste.modpython
- PythonPath "['/virtual/project/directory', '/virtual/lib/python2.4/'] + sys.path"
- PythonOption paste.ini /virtual/project/directory/pasteconfig.ini
- </Location>
-
-Some WSGI implementations assume that the SCRIPT_NAME environ variable will
-always be equal to "the root URL of the app"; Apache probably won't act as
-you expect in that case. You can add another PythonOption directive to tell
-modpython_gateway to force that behavior:
-
- PythonOption SCRIPT_NAME /mcontrol
-
-Some WSGI applications need to be cleaned up when Apache exits. You can
-register a cleanup handler with yet another PythonOption directive:
-
- PythonOption wsgi.cleanup module::function
-
-The module.function will be called with no arguments on server shutdown,
-once for each child process or thread.
-
-This module highly based on Robert Brewer's, here:
-http://projects.amor.org/misc/svn/modpython_gateway.py
-"""
-
-import traceback
-
-try:
- from mod_python import apache
-except:
- pass
-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:
- yield line
- # Notice this won't prefetch the next line; it only
- # gets called if the generator is resumed.
- line = self.readline()
-
-
-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))
-
-
-bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', "
- "when running a version of mod_python < 3.1")
-
-
-class Handler(object):
-
- def __init__(self, req):
- self.started = False
-
- options = req.get_options()
-
- # Threading and forking
- try:
- q = apache.mpm_query
- threaded = q(apache.AP_MPMQ_IS_THREADED)
- forked = q(apache.AP_MPMQ_IS_FORKED)
- except AttributeError:
- threaded = options.get('multithread', '').lower()
- if threaded == 'on':
- threaded = True
- elif threaded == 'off':
- threaded = False
- else:
- raise ValueError(bad_value % "multithread")
-
- forked = options.get('multiprocess', '').lower()
- if forked == 'on':
- forked = True
- elif forked == 'off':
- 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']
- env['PATH_INFO'] = req.uri[len(options['SCRIPT_NAME']):]
- else:
- env['SCRIPT_NAME'] = ''
- env['PATH_INFO'] = req.uri
-
- env['wsgi.input'] = InputWrapper(req)
- env['wsgi.errors'] = ErrorWrapper(req)
- env['wsgi.version'] = (1, 0)
- env['wsgi.run_once'] = False
- if env.get("HTTPS") in ('yes', 'on', '1'):
- env['wsgi.url_scheme'] = 'https'
- else:
- 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)
- for data in result:
- self.write(data)
- if not self.started:
- self.request.set_content_length(0)
- if hasattr(result, 'close'):
- result.close()
- except:
- traceback.print_exc(None, self.environ['wsgi.errors'])
- if not self.started:
- self.request.status = 500
- self.request.content_type = 'text/plain'
- 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:
- if self.started:
- raise 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))
- elif key.lower() == 'content-type':
- 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
- self.request.write(data)
-
-
-startup = None
-cleanup = None
-wsgiapps = {}
-
-def handler(req):
- options = req.get_options()
- # Run a startup function if requested.
- global startup
- if 'wsgi.startup' in options and not startup:
- func = options['wsgi.startup']
- if func:
- module_name, object_str = func.split('::', 1)
- 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:
- func = options['wsgi.cleanup']
- if func:
- module_name, object_str = func.split('::', 1)
- module = __import__(module_name, globals(), locals(), [''])
- cleanup = apache.resolve_object(module, object_str)
- def cleaner(data):
- cleanup()
- try:
- # apache.register_cleanup wasn't available until 3.1.4.
- 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')
- app = None
- if appini:
- 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/lib/paste/pony.py b/lib/paste/pony.py
@@ -1,57 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-We have a pony and/or a unicorn.
-"""
-from paste.request import construct_url
-
-PONY = """
-eJyFkkFuxCAMRfdzCisbJxK2D5D2JpbMrlI3XXQZDt9PCG0ySgcWIMT79rcN0XClUJlZRB9jVmci
-FmV19khjgRFl0RzrKmqzvY8lRUWFlXvCrD7UbAQR/17NUvGhypAF9og16vWtkC8DzUayS6pN3/dR
-ki0OnpzKjUBFpmlC7zVFRNL1rwoq6PWXXQSnIm9WoTzlM2//ke21o5g/l1ckRhiPbkDZXsKIR7l1
-36hF9uMhnRiVjI8UgYjlsIKCrXXpcA9iX5y7zMmtG0fUpW61Ssttipf6cp3WARfkMVoYFryi2a+w
-o/2dhW0OXfcMTnmh53oR9egzPs+qkpY9IKxdUVRP5wHO7UDAuI6moA2N+/z4vtc2k8B+AIBimVU=
-"""
-
-UNICORN = """
-eJyVVD1vhDAM3e9XeAtIxB5P6qlDx0OMXVBzSpZOHdsxP762E0JAnMgZ8Zn37OePAPC60eV1Dl5b
-SS7fB6DmQNGhtegpNlPIQS8HmkYGdSqNqDF9wcMYus4TuBYGsZwIPqXfEoNir5K+R3mbzhlR4JMW
-eGpikPpn9wHl2sDgEH1270guZwzKDRf3nTztMvfI5r3fJqEmNxdCyISBcWjNgjPG8Egg2hgT3mJi
-KBwNvmPB1hbWJ3TwBfMlqdTzxNyDE2H8zOD5HA4KkqJGPVY/TwnxmPA82kdSJNj7zs+R0d1pB+JO
-xn2DKgsdxAfFS2pfTSD0Fb6Uzv7dCQSvE5JmZQEQ90vNjBU1GPuGQpCPS8cGo+dQgjIKqxnJTXbw
-ucFzPFVIJXtzk6BXKGPnYsKzvFmGx7A0j6Zqvlvk5rETXbMWTGWj0RFc8QNPYVfhJfMMniCPazWJ
-lGtPZecIGJWW6oL2hpbWRZEkChe8eg5Wb7xx/MBZBFjxeZPEss+mRQ3Uhc8WQv684seSRO7i3nb4
-7HlKUg8sraz47LmXyh8S0somADvoUpoHjGWl+rUkF0H+EIf/gbyyMg58BBk6L634/fkHUCodMw==
-"""
-
-
-class PonyMiddleware(object):
-
- def __init__(self, application):
- self.application = application
-
- def __call__(self, environ, start_response):
- path_info = environ.get('PATH_INFO', '')
- if path_info == '/pony':
- url = construct_url(environ, with_query_string=False)
- if 'horn' in environ.get('QUERY_STRING', ''):
- data = UNICORN
- link = 'remove horn!'
- else:
- data = PONY
- url += '?horn'
- link = 'add horn!'
- msg = data.decode('base64').decode('zlib')
- msg = '<pre>%s\n<a href="%s">%s</a></pre>' % (
- msg, url, link)
- start_response('200 OK', [('content-type', 'text/html')])
- return [msg]
- else:
- return self.application(environ, start_response)
-
-def make_pony(app, global_conf):
- """
- Adds pony power to any application, at /pony
- """
- return PonyMiddleware(app)
-
diff --git a/lib/paste/progress.py b/lib/paste/progress.py
@@ -1,222 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-"""
-Upload Progress Monitor
-
-This is a WSGI middleware component which monitors the status of files
-being uploaded. It includes a small query application which will return
-a list of all files being uploaded by particular session/user.
-
->>> from paste.httpserver import serve
->>> from paste.urlmap import URLMap
->>> from paste.auth.basic import AuthBasicHandler
->>> from paste.debug.debugapp import SlowConsumer, SimpleApplication
->>> # from paste.progress import *
->>> realm = 'Test Realm'
->>> def authfunc(username, password):
-... return username == password
->>> map = URLMap({})
->>> ups = UploadProgressMonitor(map, threshold=1024)
->>> map['/upload'] = SlowConsumer()
->>> map['/simple'] = SimpleApplication()
->>> map['/report'] = UploadProgressReporter(ups)
->>> serve(AuthBasicHandler(ups, realm, authfunc))
-serving on...
-
-.. note::
-
- This is experimental, and will change in the future.
-"""
-import time
-from paste.wsgilib import catch_errors
-
-DEFAULT_THRESHOLD = 1024 * 1024 # one megabyte
-DEFAULT_TIMEOUT = 60*5 # five minutes
-ENVIRON_RECEIVED = 'paste.bytes_received'
-REQUEST_STARTED = 'paste.request_started'
-REQUEST_FINISHED = 'paste.request_finished'
-
-class _ProgressFile(object):
- """
- This is the input-file wrapper used to record the number of
- ``paste.bytes_received`` for the given request.
- """
-
- def __init__(self, environ, rfile):
- self._ProgressFile_environ = environ
- self._ProgressFile_rfile = rfile
- self.flush = rfile.flush
- self.write = rfile.write
- self.writelines = rfile.writelines
-
- def __iter__(self):
- environ = self._ProgressFile_environ
- riter = iter(self._ProgressFile_rfile)
- def iterwrap():
- for chunk in riter:
- environ[ENVIRON_RECEIVED] += len(chunk)
- yield chunk
- return iter(iterwrap)
-
- def read(self, size=-1):
- chunk = self._ProgressFile_rfile.read(size)
- self._ProgressFile_environ[ENVIRON_RECEIVED] += len(chunk)
- return chunk
-
- def readline(self):
- chunk = self._ProgressFile_rfile.readline()
- self._ProgressFile_environ[ENVIRON_RECEIVED] += len(chunk)
- return chunk
-
- def readlines(self, hint=None):
- chunk = self._ProgressFile_rfile.readlines(hint)
- self._ProgressFile_environ[ENVIRON_RECEIVED] += len(chunk)
- return chunk
-
-class UploadProgressMonitor(object):
- """
- monitors and reports on the status of uploads in progress
-
- Parameters:
-
- ``application``
-
- This is the next application in the WSGI stack.
-
- ``threshold``
-
- This is the size in bytes that is needed for the
- upload to be included in the monitor.
-
- ``timeout``
-
- This is the amount of time (in seconds) that a upload
- remains in the monitor after it has finished.
-
- Methods:
-
- ``uploads()``
-
- This returns a list of ``environ`` dict objects for each
- upload being currently monitored, or finished but whose time
- has not yet expired.
-
- For each request ``environ`` that is monitored, there are several
- variables that are stored:
-
- ``paste.bytes_received``
-
- This is the total number of bytes received for the given
- request; it can be compared with ``CONTENT_LENGTH`` to
- build a percentage complete. This is an integer value.
-
- ``paste.request_started``
-
- This is the time (in seconds) when the request was started
- as obtained from ``time.time()``. One would want to format
- this for presentation to the user, if necessary.
-
- ``paste.request_finished``
-
- This is the time (in seconds) when the request was finished,
- canceled, or otherwise disconnected. This is None while
- the given upload is still in-progress.
-
- TODO: turn monitor into a queue and purge queue of finished
- requests that have passed the timeout period.
- """
- def __init__(self, application, threshold=None, timeout=None):
- self.application = application
- self.threshold = threshold or DEFAULT_THRESHOLD
- self.timeout = timeout or DEFAULT_TIMEOUT
- self.monitor = []
-
- def __call__(self, environ, start_response):
- length = environ.get('CONTENT_LENGTH', 0)
- if length and int(length) > self.threshold:
- # replace input file object
- self.monitor.append(environ)
- environ[ENVIRON_RECEIVED] = 0
- environ[REQUEST_STARTED] = time.time()
- environ[REQUEST_FINISHED] = None
- environ['wsgi.input'] = \
- _ProgressFile(environ, environ['wsgi.input'])
- def finalizer(exc_info=None):
- environ[REQUEST_FINISHED] = time.time()
- return catch_errors(self.application, environ,
- start_response, finalizer, finalizer)
- return self.application(environ, start_response)
-
- def uploads(self):
- return self.monitor
-
-class UploadProgressReporter(object):
- """
- reports on the progress of uploads for a given user
-
- This reporter returns a JSON file (for use in AJAX) listing the
- uploads in progress for the given user. By default, this reporter
- uses the ``REMOTE_USER`` environment to compare between the current
- request and uploads in-progress. If they match, then a response
- record is formed.
-
- ``match()``
-
- This member function can be overriden to provide alternative
- matching criteria. It takes two environments, the first
- is the current request, the second is a current upload.
-
- ``report()``
-
- This member function takes an environment and builds a
- ``dict`` that will be used to create a JSON mapping for
- the given upload. By default, this just includes the
- percent complete and the request url.
-
- """
- def __init__(self, monitor):
- self.monitor = monitor
-
- def match(self, search_environ, upload_environ):
- if search_environ.get('REMOTE_USER', None) == \
- upload_environ.get('REMOTE_USER', 0):
- return True
- return False
-
- def report(self, environ):
- retval = { 'started': time.strftime("%Y-%m-%d %H:%M:%S",
- time.gmtime(environ[REQUEST_STARTED])),
- 'finished': '',
- 'content_length': environ.get('CONTENT_LENGTH'),
- 'bytes_received': environ[ENVIRON_RECEIVED],
- 'path_info': environ.get('PATH_INFO',''),
- 'query_string': environ.get('QUERY_STRING','')}
- finished = environ[REQUEST_FINISHED]
- if finished:
- retval['finished'] = time.strftime("%Y:%m:%d %H:%M:%S",
- time.gmtime(finished))
- return retval
-
- def __call__(self, environ, start_response):
- body = []
- for map in [self.report(env) for env in self.monitor.uploads()
- if self.match(environ, env)]:
- parts = []
- for k, v in map.items():
- v = str(v).replace("\\", "\\\\").replace('"', '\\"')
- parts.append('%s: "%s"' % (k, v))
- body.append("{ %s }" % ", ".join(parts))
- body = "[ %s ]" % ", ".join(body)
- start_response("200 OK", [('Content-Type', 'text/plain'),
- ('Content-Length', len(body))])
- return [body]
-
-__all__ = ['UploadProgressMonitor', 'UploadProgressReporter']
-
-if "__main__" == __name__:
- import doctest
- doctest.testmod(optionflags=doctest.ELLIPSIS)
diff --git a/lib/paste/proxy.py b/lib/paste/proxy.py
@@ -1,276 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-An application that proxies WSGI requests to a remote server.
-
-TODO:
-
-* Send ``Via`` header? It's not clear to me this is a Via in the
- style of a typical proxy.
-
-* Other headers or metadata? I put in X-Forwarded-For, but that's it.
-
-* Signed data of non-HTTP keys? This would be for things like
- REMOTE_USER.
-
-* Something to indicate what the original URL was? The original host,
- scheme, and base path.
-
-* Rewriting ``Location`` headers? mod_proxy does this.
-
-* Rewriting body? (Probably not on this one -- that can be done with
- a different middleware that wraps this middleware)
-
-* Example::
-
- use = egg:Paste#proxy
- address = http://server3:8680/exist/rest/db/orgs/sch/config/
- allowed_request_methods = GET
-
-"""
-
-import httplib
-import urlparse
-import urllib
-
-from paste import httpexceptions
-from paste.util.converters import aslist
-
-# Remove these headers from response (specify lower case header
-# names):
-filtered_headers = (
- 'transfer-encoding',
- 'connection',
- 'keep-alive',
- 'proxy-authenticate',
- 'proxy-authorization',
- 'te',
- 'trailers',
- 'upgrade',
-)
-
-class Proxy(object):
-
- def __init__(self, address, allowed_request_methods=(),
- suppress_http_headers=()):
- self.address = address
- self.parsed = urlparse.urlsplit(address)
- self.scheme = self.parsed[0].lower()
- self.host = self.parsed[1]
- 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
- environ['REQUEST_METHOD'].lower() not in self.allowed_request_methods):
- return httpexceptions.HTTPBadRequest("Disallowed")(environ, start_response)
-
- if self.scheme == 'http':
- ConnClass = httplib.HTTPConnection
- elif self.scheme == 'https':
- ConnClass = httplib.HTTPSConnection
- else:
- raise ValueError(
- "Unknown scheme for %r: %r" % (self.address, self.scheme))
- conn = ConnClass(self.host)
- headers = {}
- for key, value in environ.items():
- if key.startswith('HTTP_'):
- key = key[5:].lower().replace('_', '-')
- if key == 'host' or key in self.suppress_http_headers:
- continue
- headers[key] = value
- headers['host'] = self.host
- if 'REMOTE_ADDR' in environ:
- headers['x-forwarded-for'] = environ['REMOTE_ADDR']
- if environ.get('CONTENT_TYPE'):
- headers['content-type'] = environ['CONTENT_TYPE']
- if environ.get('CONTENT_LENGTH'):
- headers['content-length'] = environ['CONTENT_LENGTH']
- length = int(environ['CONTENT_LENGTH'])
- body = environ['wsgi.input'].read(length)
- else:
- body = ''
-
- path_info = urllib.quote(environ['PATH_INFO'])
- if self.path:
- request_path = path_info
- if 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?
- length = res.getheader('content-length')
- if length is not None:
- body = res.read(int(length))
- else:
- body = res.read()
- conn.close()
- return [body]
-
-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
- host
- """
- allowed_request_methods = aslist(allowed_request_methods)
- suppress_http_headers = aslist(suppress_http_headers)
- return Proxy(
- address,
- allowed_request_methods=allowed_request_methods,
- suppress_http_headers=suppress_http_headers)
-
-
-class TransparentProxy(object):
-
- """
- A proxy that sends the request just as it was given, including
- respecting HTTP_HOST, wsgi.url_scheme, etc.
-
- This is a way of translating WSGI requests directly to real HTTP
- requests. All information goes in the environment; modify it to
- modify the way the request is made.
-
- If you specify ``force_host`` (and optionally ``force_scheme``)
- then HTTP_HOST won't be used to determine where to connect to;
- instead a specific host will be connected to, but the ``Host``
- header in the request will remain intact.
- """
-
- def __init__(self, force_host=None,
- force_scheme='http'):
- self.force_host = force_host
- self.force_scheme = force_scheme
-
- def __repr__(self):
- return '<%s %s force_host=%r force_scheme=%r>' % (
- self.__class__.__name__,
- hex(id(self)),
- self.force_host, self.force_scheme)
-
- def __call__(self, environ, start_response):
- scheme = environ['wsgi.url_scheme']
- if self.force_host is None:
- conn_scheme = scheme
- else:
- conn_scheme = self.force_scheme
- if conn_scheme == 'http':
- ConnClass = httplib.HTTPConnection
- elif conn_scheme == 'https':
- ConnClass = httplib.HTTPSConnection
- else:
- raise ValueError(
- "Unknown scheme %r" % scheme)
- if 'HTTP_HOST' not in environ:
- raise ValueError(
- "WSGI environ must contain an HTTP_HOST key")
- host = environ['HTTP_HOST']
- if self.force_host is None:
- conn_host = host
- else:
- conn_host = self.force_host
- conn = ConnClass(conn_host)
- headers = {}
- for key, value in environ.items():
- if key.startswith('HTTP_'):
- key = key[5:].lower().replace('_', '-')
- headers[key] = value
- headers['host'] = host
- if 'REMOTE_ADDR' in environ and 'HTTP_X_FORWARDED_FOR' not in environ:
- headers['x-forwarded-for'] = environ['REMOTE_ADDR']
- if environ.get('CONTENT_TYPE'):
- headers['content-type'] = environ['CONTENT_TYPE']
- if environ.get('CONTENT_LENGTH'):
- length = int(environ['CONTENT_LENGTH'])
- body = environ['wsgi.input'].read(length)
- elif 'CONTENT_LENGTH' not in environ:
- body = ''
- length = 0
- else:
- body = ''
- length = 0
-
- path = (environ.get('SCRIPT_NAME', '')
- + environ.get('PATH_INFO', ''))
- path = urllib.quote(path)
- if 'QUERY_STRING' in environ:
- 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?
- length = res.getheader('content-length')
- if length is not None:
- body = res.read(int(length))
- else:
- body = res.read()
- conn.close()
- return [body]
-
-def parse_headers(message):
- """
- Turn a Message object into a list of WSGI-style headers.
- """
- headers_out = []
- for full_header in message.headers:
- if not full_header:
- # Shouldn't happen, but we'll just ignore
- continue
- if full_header[0].isspace():
- # Continuation line, add to the last header
- if not headers_out:
- raise ValueError(
- "First header starts with a space (%r)" % full_header)
- last_header, last_value = headers_out.pop()
- value = last_value + ' ' + full_header.strip()
- headers_out.append((last_header, value))
- continue
- try:
- header, value = full_header.split(':', 1)
- except:
- raise ValueError("Invalid header: %r" % full_header)
- value = value.strip()
- if header.lower() not in filtered_headers:
- headers_out.append((header, value))
- return headers_out
-
-def make_transparent_proxy(
- global_conf, force_host=None, force_scheme='http'):
- """
- Create a proxy that connects to a specific host, but does
- absolutely no other filtering, including the Host header.
- """
- return TransparentProxy(force_host=force_host,
- force_scheme=force_scheme)
diff --git a/lib/paste/recursive.py b/lib/paste/recursive.py
@@ -1,401 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Middleware to make internal requests and forward requests internally.
-
-When applied, several keys are added to the environment that will allow
-you to trigger recursive redirects and forwards.
-
- paste.recursive.include:
- When you call
- ``environ['paste.recursive.include'](new_path_info)`` a response
- will be returned. The response has a ``body`` attribute, a
- ``status`` attribute, and a ``headers`` attribute.
-
- paste.recursive.script_name:
- The ``SCRIPT_NAME`` at the point that recursive lives. Only
- paths underneath this path can be redirected to.
-
- paste.recursive.old_path_info:
- A list of previous ``PATH_INFO`` values from previous redirects.
-
-Raise ``ForwardRequestException(new_path_info)`` to do a forward
-(aborting the current request).
-"""
-
-from cStringIO import StringIO
-import warnings
-
-__all__ = ['RecursiveMiddleware']
-__pudge_all__ = ['RecursiveMiddleware', 'ForwardRequestException']
-
-class CheckForRecursionMiddleware(object):
- def __init__(self, app, env):
- self.app = app
- self.env = env
-
- def __call__(self, environ, start_response):
- path_info = environ.get('PATH_INFO','')
- if path_info in self.env.get(
- 'paste.recursive.old_path_info', []):
- raise AssertionError(
- "Forwarding loop detected; %r visited twice (internal "
- "redirect path: %s)"
- % (path_info, self.env['paste.recursive.old_path_info']))
- old_path_info = self.env.setdefault('paste.recursive.old_path_info', [])
- old_path_info.append(self.env.get('PATH_INFO', ''))
- return self.app(environ, start_response)
-
-class RecursiveMiddleware(object):
-
- """
- A WSGI middleware that allows for recursive and forwarded calls.
- All these calls go to the same 'application', but presumably that
- application acts differently with different URLs. The forwarded
- URLs must be relative to this container.
-
- Interface is entirely through the ``paste.recursive.forward`` and
- ``paste.recursive.include`` environmental keys.
- """
-
- def __init__(self, application, global_conf=None):
- self.application = application
-
- def __call__(self, environ, start_response):
- environ['paste.recursive.forward'] = Forwarder(
- self.application,
- environ,
- start_response)
- environ['paste.recursive.include'] = Includer(
- self.application,
- environ,
- start_response)
- environ['paste.recursive.include_app_iter'] = IncluderAppIter(
- self.application,
- environ,
- start_response)
- my_script_name = environ.get('SCRIPT_NAME', '')
- environ['paste.recursive.script_name'] = my_script_name
- try:
- return self.application(environ, start_response)
- except ForwardRequestException, e:
- middleware = CheckForRecursionMiddleware(
- e.factory(self), environ)
- return middleware(environ, start_response)
-
-class ForwardRequestException(Exception):
- """
- Used to signal that a request should be forwarded to a different location.
-
- ``url``
- The URL to forward to starting with a ``/`` and relative to
- ``RecursiveMiddleware``. URL fragments can also contain query strings
- so ``/error?code=404`` would be a valid URL fragment.
-
- ``environ``
- An altertative WSGI environment dictionary to use for the forwarded
- request. If specified is used *instead* of the ``url_fragment``
-
- ``factory``
- If specifed ``factory`` is used instead of ``url`` or ``environ``.
- ``factory`` is a callable that takes a WSGI application object
- as the first argument and returns an initialised WSGI middleware
- which can alter the forwarded response.
-
- Basic usage (must have ``RecursiveMiddleware`` present) :
-
- .. code-block:: python
-
- from paste.recursive import ForwardRequestException
- def app(environ, start_response):
- if environ['PATH_INFO'] == '/hello':
- start_response("200 OK", [('Content-type', 'text/plain')])
- return ['Hello World!']
- elif environ['PATH_INFO'] == '/error':
- start_response("404 Not Found", [('Content-type', 'text/plain')])
- return ['Page not found']
- else:
- raise ForwardRequestException('/error')
-
- from paste.recursive import RecursiveMiddleware
- app = RecursiveMiddleware(app)
-
- If you ran this application and visited ``/hello`` you would get a
- ``Hello World!`` message. If you ran the application and visited
- ``/not_found`` a ``ForwardRequestException`` would be raised and the caught
- by the ``RecursiveMiddleware``. The ``RecursiveMiddleware`` would then
- return the headers and response from the ``/error`` URL but would display
- a ``404 Not found`` status message.
-
- You could also specify an ``environ`` dictionary instead of a url. Using
- the same example as before:
-
- .. code-block:: python
-
- def app(environ, start_response):
- ... same as previous example ...
- else:
- new_environ = environ.copy()
- new_environ['PATH_INFO'] = '/error'
- raise ForwardRequestException(environ=new_environ)
-
- Finally, if you want complete control over every aspect of the forward you
- can specify a middleware factory. For example to keep the old status code
- but use the headers and resposne body from the forwarded response you might
- do this:
-
- .. code-block:: python
-
- from paste.recursive import ForwardRequestException
- from paste.recursive import RecursiveMiddleware
- from paste.errordocument import StatusKeeper
-
- def app(environ, start_response):
- if environ['PATH_INFO'] == '/hello':
- start_response("200 OK", [('Content-type', 'text/plain')])
- return ['Hello World!']
- elif environ['PATH_INFO'] == '/error':
- start_response("404 Not Found", [('Content-type', 'text/plain')])
- return ['Page not found']
- else:
- def factory(app):
- return StatusKeeper(app, status='404 Not Found', url='/error')
- raise ForwardRequestException(factory=factory)
-
- app = RecursiveMiddleware(app)
- """
-
- def __init__(
- self,
- url=None,
- environ={},
- factory=None,
- path_info=None):
- # Check no incompatible options have been chosen
- if factory and url:
- raise TypeError(
- 'You cannot specify factory and a url in '
- 'ForwardRequestException')
- elif factory and environ:
- raise TypeError(
- 'You cannot specify factory and environ in '
- 'ForwardRequestException')
- if url and environ:
- raise TypeError(
- 'You cannot specify environ and url in '
- 'ForwardRequestException')
-
- # set the path_info or warn about its use.
- if path_info:
- if not url:
- warnings.warn(
- "ForwardRequestException(path_info=...) has been deprecated; please "
- "use ForwardRequestException(url=...)",
- DeprecationWarning, 2)
- else:
- raise TypeError('You cannot use url and path_info in ForwardRequestException')
- self.path_info = path_info
-
- # If the url can be treated as a path_info do that
- if url and not '?' in str(url):
- self.path_info = url
-
- # Base middleware
- class ForwardRequestExceptionMiddleware(object):
- def __init__(self, app):
- self.app = app
-
- # Otherwise construct the appropriate middleware factory
- if hasattr(self, 'path_info'):
- p = self.path_info
- def factory_(app):
- class PathInfoForward(ForwardRequestExceptionMiddleware):
- def __call__(self, environ, start_response):
- environ['PATH_INFO'] = p
- return self.app(environ, start_response)
- return PathInfoForward(app)
- self.factory = factory_
- elif url:
- def factory_(app):
- class URLForward(ForwardRequestExceptionMiddleware):
- def __call__(self, environ, start_response):
- environ['PATH_INFO'] = url.split('?')[0]
- environ['QUERY_STRING'] = url.split('?')[1]
- return self.app(environ, start_response)
- return URLForward(app)
- self.factory = factory_
- elif environ:
- def factory_(app):
- class EnvironForward(ForwardRequestExceptionMiddleware):
- def __call__(self, environ_, start_response):
- return self.app(environ, start_response)
- return EnvironForward(app)
- self.factory = factory_
- else:
- self.factory = factory
-
-class Recursive(object):
-
- def __init__(self, application, environ, start_response):
- self.application = application
- self.original_environ = environ.copy()
- self.previous_environ = environ
- self.start_response = start_response
-
- def __call__(self, path, extra_environ=None):
- """
- `extra_environ` is an optional dictionary that is also added
- to the forwarded request. E.g., ``{'HTTP_HOST': 'new.host'}``
- could be used to forward to a different virtual host.
- """
- environ = self.original_environ.copy()
- if extra_environ:
- environ.update(extra_environ)
- environ['paste.recursive.previous_environ'] = self.previous_environ
- base_path = self.original_environ.get('SCRIPT_NAME')
- if path.startswith('/'):
- assert path.startswith(base_path), (
- "You can only forward requests to resources under the "
- "path %r (not %r)" % (base_path, path))
- path = path[len(base_path)+1:]
- assert not path.startswith('/')
- path_info = '/' + path
- environ['PATH_INFO'] = path_info
- environ['REQUEST_METHOD'] = 'GET'
- environ['CONTENT_LENGTH'] = '0'
- environ['CONTENT_TYPE'] = ''
- environ['wsgi.input'] = StringIO('')
- return self.activate(environ)
-
- def activate(self, environ):
- raise NotImplementedError
-
- def __repr__(self):
- return '<%s.%s from %s>' % (
- self.__class__.__module__,
- self.__class__.__name__,
- self.original_environ.get('SCRIPT_NAME') or '/')
-
-class Forwarder(Recursive):
-
- """
- The forwarder will try to restart the request, except with
- the new `path` (replacing ``PATH_INFO`` in the request).
-
- It must not be called after and headers have been returned.
- It returns an iterator that must be returned back up the call
- stack, so it must be used like:
-
- .. code-block:: python
-
- return environ['paste.recursive.forward'](path)
-
- Meaningful transformations cannot be done, since headers are
- sent directly to the server and cannot be inspected or
- rewritten.
- """
-
- def activate(self, environ):
- warnings.warn(
- "recursive.Forwarder has been deprecated; please use "
- "ForwardRequestException",
- DeprecationWarning, 2)
- return self.application(environ, self.start_response)
-
-
-class Includer(Recursive):
-
- """
- Starts another request with the given path and adding or
- overwriting any values in the `extra_environ` dictionary.
- Returns an IncludeResponse object.
- """
-
- def activate(self, environ):
- response = IncludedResponse()
- def start_response(status, headers, exc_info=None):
- if exc_info:
- raise exc_info[0], exc_info[1], exc_info[2]
- response.status = status
- response.headers = headers
- return response.write
- app_iter = self.application(environ, start_response)
- try:
- for s in app_iter:
- response.write(s)
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- response.close()
- return response
-
-class IncludedResponse(object):
-
- def __init__(self):
- self.headers = None
- self.status = None
- self.output = StringIO()
- self.str = None
-
- def close(self):
- self.str = self.output.getvalue()
- self.output.close()
- self.output = None
-
- def write(self, s):
- assert self.output is not None, (
- "This response has already been closed and no further data "
- "can be written.")
- self.output.write(s)
-
- def __str__(self):
- return self.body
-
- def body__get(self):
- if self.str is None:
- return self.output.getvalue()
- else:
- return self.str
- body = property(body__get)
-
-
-class IncluderAppIter(Recursive):
- """
- Like Includer, but just stores the app_iter response
- (be sure to call close on the response!)
- """
-
- def activate(self, environ):
- response = IncludedAppIterResponse()
- def start_response(status, headers, exc_info=None):
- if exc_info:
- raise exc_info[0], exc_info[1], exc_info[2]
- response.status = status
- response.headers = headers
- return response.write
- app_iter = self.application(environ, start_response)
- response.app_iter = app_iter
- return response
-
-class IncludedAppIterResponse(object):
-
- def __init__(self):
- self.status = None
- self.headers = None
- self.accumulated = []
- self.app_iter = None
- self._closed = False
-
- def close(self):
- assert not self._closed, (
- "Tried to close twice")
- if hasattr(self.app_iter, 'close'):
- self.app_iter.close()
-
- def write(self, s):
- self.accumulated.append
-
-def make_recursive_middleware(app, global_conf):
- return RecursiveMiddleware(app)
-
-make_recursive_middleware.__doc__ = __doc__
diff --git a/lib/paste/registry.py b/lib/paste/registry.py
@@ -1,552 +0,0 @@
-# (c) 2005 Ben Bangert
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-"""Registry for handling request-local module globals sanely
-
-Dealing with module globals in a thread-safe way is good if your
-application is the sole responder in a thread, however that approach fails
-to properly account for various scenarios that occur with WSGI applications
-and middleware.
-
-What is actually needed in the case where a module global is desired that
-is always set properly depending on the current request, is a stacked
-thread-local object. Such an object is popped or pushed during the request
-cycle so that it properly represents the object that should be active for
-the current request.
-
-To make it easy to deal with such variables, this module provides a special
-StackedObjectProxy class which you can instantiate and attach to your
-module where you'd like others to access it. The object you'd like this to
-actually "be" during the request is then registered with the
-RegistryManager middleware, which ensures that for the scope of the current
-WSGI application everything will work properly.
-
-Example:
-
-.. code-block:: python
-
- #yourpackage/__init__.py
-
- from paste.registry import RegistryManager, StackedObjectProxy
- myglobal = StackedObjectProxy()
-
- #wsgi app stack
- app = RegistryManager(yourapp)
-
- #inside your wsgi app
- class yourapp(object):
- def __call__(self, environ, start_response):
- obj = someobject # The request-local object you want to access
- # via yourpackage.myglobal
- if environ.has_key('paste.registry'):
- environ['paste.registry'].register(myglobal, obj)
-
-You will then be able to import yourpackage anywhere in your WSGI app or in
-the calling stack below it and be assured that it is using the object you
-registered with Registry.
-
-RegistryManager can be in the WSGI stack multiple times, each time it
-appears it registers a new request context.
-
-
-Performance
-===========
-
-The overhead of the proxy object is very minimal, however if you are using
-proxy objects extensively (Thousands of accesses per request or more), there
-are some ways to avoid them. A proxy object runs approximately 3-20x slower
-than direct access to the object, this is rarely your performance bottleneck
-when developing web applications.
-
-Should you be developing a system which may be accessing the proxy object
-thousands of times per request, the performance of the proxy will start to
-become more noticeable. In that circumstance, the problem can be avoided by
-getting at the actual object via the proxy with the ``_current_obj`` function:
-
-.. code-block:: python
-
- #sessions.py
- Session = StackedObjectProxy()
- # ... initialization code, etc.
-
- # somemodule.py
- import sessions
-
- def somefunc():
- session = sessions.Session._current_obj()
- # ... tons of session access
-
-This way the proxy is used only once to retrieve the object for the current
-context and the overhead is minimized while still making it easy to access
-the underlying object. The ``_current_obj`` function is preceded by an
-underscore to more likely avoid clashing with the contained object's
-attributes.
-
-**NOTE:** This is *highly* unlikely to be an issue in the vast majority of
-cases, and requires incredibly large amounts of proxy object access before
-one should consider the proxy object to be causing slow-downs. This section
-is provided solely in the extremely rare case that it is an issue so that a
-quick way to work around it is documented.
-
-"""
-import sys
-import paste.util.threadinglocal as threadinglocal
-
-__all__ = ['StackedObjectProxy', 'RegistryManager', 'StackedObjectRestorer',
- 'restorer']
-
-class NoDefault(object): pass
-
-class StackedObjectProxy(object):
- """Track an object instance internally using a stack
-
- The StackedObjectProxy proxies access to an object internally using a
- stacked thread-local. This makes it safe for complex WSGI environments
- where access to the object may be desired in multiple places without
- having to pass the actual object around.
-
- New objects are added to the top of the stack with _push_object while
- objects can be removed with _pop_object.
-
- """
- def __init__(self, default=NoDefault, name="Default"):
- """Create a new StackedObjectProxy
-
- If a default is given, its used in every thread if no other object
- has been pushed on.
-
- """
- self.__dict__['____name__'] = name
- self.__dict__['____local__'] = threadinglocal.local()
- if default is not NoDefault:
- self.__dict__['____default_object__'] = default
-
- def __dir__(self):
- """Return a list of the StackedObjectProxy's and proxied
- object's (if one exists) names.
- """
- dir_list = dir(self.__class__) + self.__dict__.keys()
- try:
- dir_list.extend(dir(self._current_obj()))
- except TypeError:
- pass
- dir_list.sort()
- return dir_list
-
- def __getattr__(self, attr):
- return getattr(self._current_obj(), attr)
-
- def __setattr__(self, attr, value):
- setattr(self._current_obj(), attr, value)
-
- def __delattr__(self, name):
- delattr(self._current_obj(), name)
-
- def __getitem__(self, key):
- return self._current_obj()[key]
-
- def __setitem__(self, key, value):
- self._current_obj()[key] = value
-
- def __delitem__(self, key):
- del self._current_obj()[key]
-
- def __call__(self, *args, **kw):
- return self._current_obj()(*args, **kw)
-
- def __repr__(self):
- try:
- return repr(self._current_obj())
- except (TypeError, AttributeError):
- return '<%s.%s object at 0x%x>' % (self.__class__.__module__,
- self.__class__.__name__,
- id(self))
-
- def __iter__(self):
- return iter(self._current_obj())
-
- def __len__(self):
- return len(self._current_obj())
-
- def __contains__(self, key):
- return key in self._current_obj()
-
- def __nonzero__(self):
- return bool(self._current_obj())
-
- def _current_obj(self):
- """Returns the current active object being proxied to
-
- In the event that no object was pushed, the default object if
- provided will be used. Otherwise, a TypeError will be raised.
-
- """
- objects = getattr(self.____local__, 'objects', None)
- if objects:
- return objects[-1]
- else:
- obj = self.__dict__.get('____default_object__', NoDefault)
- if obj is not NoDefault:
- return obj
- else:
- raise TypeError(
- 'No object (name: %s) has been registered for this '
- 'thread' % self.____name__)
-
- def _push_object(self, obj):
- """Make ``obj`` the active object for this thread-local.
-
- This should be used like:
-
- .. code-block:: python
-
- obj = yourobject()
- module.glob = StackedObjectProxy()
- module.glob._push_object(obj)
- try:
- ... do stuff ...
- finally:
- module.glob._pop_object(conf)
-
- """
- if not hasattr(self.____local__, 'objects'):
- self.____local__.objects = []
- self.____local__.objects.append(obj)
-
- def _pop_object(self, obj=None):
- """Remove a thread-local object.
-
- If ``obj`` is given, it is checked against the popped object and an
- error is emitted if they don't match.
-
- """
- if not hasattr(self.____local__, 'objects'):
- raise AssertionError('No object has been registered for this thread')
- popped = self.____local__.objects.pop()
- if obj:
- if popped is not obj:
- raise AssertionError(
- 'The object popped (%s) is not the same as the object '
- 'expected (%s)' % (popped, obj))
-
- def _object_stack(self):
- """Returns all of the objects stacked in this container
-
- (Might return [] if there are none)
- """
- try:
- return self.____local__.objects[:]
- except AssertionError:
- return []
-
- # The following methods will be swapped for their original versions by
- # StackedObjectRestorer when restoration is enabled. The original
- # functions (e.g. _current_obj) will be available at _current_obj_orig
-
- def _current_obj_restoration(self):
- request_id = restorer.in_restoration()
- if request_id:
- return restorer.get_saved_proxied_obj(self, request_id)
- return self._current_obj_orig()
- _current_obj_restoration.__doc__ = \
- ('%s\n(StackedObjectRestorer restoration enabled)' % \
- _current_obj.__doc__)
-
- def _push_object_restoration(self, obj):
- if not restorer.in_restoration():
- self._push_object_orig(obj)
- _push_object_restoration.__doc__ = \
- ('%s\n(StackedObjectRestorer restoration enabled)' % \
- _push_object.__doc__)
-
- def _pop_object_restoration(self, obj=None):
- if not restorer.in_restoration():
- self._pop_object_orig(obj)
- _pop_object_restoration.__doc__ = \
- ('%s\n(StackedObjectRestorer restoration enabled)' % \
- _pop_object.__doc__)
-
-class Registry(object):
- """Track objects and stacked object proxies for removal
-
- The Registry object is instantiated a single time for the request no
- matter how many times the RegistryManager is used in a WSGI stack. Each
- RegistryManager must call ``prepare`` before continuing the call to
- start a new context for object registering.
-
- Each context is tracked with a dict inside a list. The last list
- element is the currently executing context. Each context dict is keyed
- by the id of the StackedObjectProxy instance being proxied, the value
- is a tuple of the StackedObjectProxy instance and the object being
- tracked.
-
- """
- def __init__(self):
- """Create a new Registry object
-
- ``prepare`` must still be called before this Registry object can be
- used to register objects.
-
- """
- self.reglist = []
-
- def prepare(self):
- """Used to create a new registry context
-
- Anytime a new RegistryManager is called, ``prepare`` needs to be
- called on the existing Registry object. This sets up a new context
- for registering objects.
-
- """
- self.reglist.append({})
-
- def register(self, stacked, obj):
- """Register an object with a StackedObjectProxy"""
- myreglist = self.reglist[-1]
- stacked_id = id(stacked)
- if stacked_id in myreglist:
- stacked._pop_object(myreglist[stacked_id][1])
- del myreglist[stacked_id]
- stacked._push_object(obj)
- myreglist[stacked_id] = (stacked, obj)
-
- # Replace now does the same thing as register
- replace = register
-
- def cleanup(self):
- """Remove all objects from all StackedObjectProxy instances that
- were tracked at this Registry context"""
- for stacked, obj in self.reglist[-1].itervalues():
- stacked._pop_object(obj)
- self.reglist.pop()
-
-class RegistryManager(object):
- """Creates and maintains a Registry context
-
- RegistryManager creates a new registry context for the registration of
- StackedObjectProxy instances. Multiple RegistryManager's can be in a
- WSGI stack and will manage the context so that the StackedObjectProxies
- always proxy to the proper object.
-
- The object being registered can be any object sub-class, list, or dict.
-
- Registering objects is done inside a WSGI application under the
- RegistryManager instance, using the ``environ['paste.registry']``
- object which is a Registry instance.
-
- """
- def __init__(self, application, streaming=False):
- self.application = application
- self.streaming = streaming
-
- def __call__(self, environ, start_response):
- app_iter = None
- reg = environ.setdefault('paste.registry', Registry())
- reg.prepare()
- if self.streaming:
- return self.streaming_iter(reg, environ, start_response)
-
- try:
- app_iter = self.application(environ, start_response)
- except Exception, e:
- # Regardless of if the content is an iterable, generator, list
- # or tuple, we clean-up right now. If its an iterable/generator
- # care should be used to ensure the generator has its own ref
- # to the actual object
- if environ.get('paste.evalexception'):
- # EvalException is present in the WSGI stack
- expected = False
- for expect in environ.get('paste.expected_exceptions', []):
- if isinstance(e, expect):
- expected = True
- if not expected:
- # An unexpected exception: save state for EvalException
- restorer.save_registry_state(environ)
- reg.cleanup()
- raise
- except:
- # Save state for EvalException if it's present
- if environ.get('paste.evalexception'):
- restorer.save_registry_state(environ)
- reg.cleanup()
- raise
- else:
- reg.cleanup()
-
- return app_iter
-
- def streaming_iter(self, reg, environ, start_response):
- try:
- for item in self.application(environ, start_response):
- yield item
- except Exception, e:
- # Regardless of if the content is an iterable, generator, list
- # or tuple, we clean-up right now. If its an iterable/generator
- # care should be used to ensure the generator has its own ref
- # to the actual object
- if environ.get('paste.evalexception'):
- # EvalException is present in the WSGI stack
- expected = False
- for expect in environ.get('paste.expected_exceptions', []):
- if isinstance(e, expect):
- expected = True
- if not expected:
- # An unexpected exception: save state for EvalException
- restorer.save_registry_state(environ)
- reg.cleanup()
- raise
- except:
- # Save state for EvalException if it's present
- if environ.get('paste.evalexception'):
- restorer.save_registry_state(environ)
- reg.cleanup()
- raise
- else:
- reg.cleanup()
-
-
-class StackedObjectRestorer(object):
- """Track StackedObjectProxies and their proxied objects for automatic
- restoration within EvalException's interactive debugger.
-
- An instance of this class tracks all StackedObjectProxy state in existence
- when unexpected exceptions are raised by WSGI applications housed by
- EvalException and RegistryManager. Like EvalException, this information is
- stored for the life of the process.
-
- When an unexpected exception occurs and EvalException is present in the
- WSGI stack, save_registry_state is intended to be called to store the
- Registry state and enable automatic restoration on all currently registered
- StackedObjectProxies.
-
- With restoration enabled, those StackedObjectProxies' _current_obj
- (overwritten by _current_obj_restoration) method's strategy is modified:
- it will return its appropriate proxied object from the restorer when
- a restoration context is active in the current thread.
-
- The StackedObjectProxies' _push/pop_object methods strategies are also
- changed: they no-op when a restoration context is active in the current
- thread (because the pushing/popping work is all handled by the
- Registry/restorer).
-
- The request's Registry objects' reglists are restored from the restorer
- when a restoration context begins, enabling the Registry methods to work
- while their changes are tracked by the restorer.
-
- The overhead of enabling restoration is negligible (another threadlocal
- access for the changed StackedObjectProxy methods) for normal use outside
- of a restoration context, but worth mentioning when combined with
- StackedObjectProxies normal overhead. Once enabled it does not turn off,
- however:
-
- o Enabling restoration only occurs after an unexpected exception is
- detected. The server is likely to be restarted shortly after the exception
- is raised to fix the cause
-
- o StackedObjectRestorer is only enabled when EvalException is enabled (not
- on a production server) and RegistryManager exists in the middleware
- stack"""
- def __init__(self):
- # Registries and their saved reglists by request_id
- self.saved_registry_states = {}
- self.restoration_context_id = threadinglocal.local()
-
- def save_registry_state(self, environ):
- """Save the state of this request's Registry (if it hasn't already been
- saved) to the saved_registry_states dict, keyed by the request's unique
- identifier"""
- registry = environ.get('paste.registry')
- if not registry or not len(registry.reglist) or \
- self.get_request_id(environ) in self.saved_registry_states:
- # No Registry, no state to save, or this request's state has
- # already been saved
- return
-
- self.saved_registry_states[self.get_request_id(environ)] = \
- (registry, registry.reglist[:])
-
- # Tweak the StackedObjectProxies we want to save state for -- change
- # their methods to act differently when a restoration context is active
- # in the current thread
- for reglist in registry.reglist:
- for stacked, obj in reglist.itervalues():
- self.enable_restoration(stacked)
-
- def get_saved_proxied_obj(self, stacked, request_id):
- """Retrieve the saved object proxied by the specified
- StackedObjectProxy for the request identified by request_id"""
- # All state for the request identified by request_id
- reglist = self.saved_registry_states[request_id][1]
-
- # The top of the stack was current when the exception occurred
- stack_level = len(reglist) - 1
- stacked_id = id(stacked)
- while True:
- if stack_level < 0:
- # Nothing registered: Call _current_obj_orig to raise a
- # TypeError
- return stacked._current_obj_orig()
- context = reglist[stack_level]
- if stacked_id in context:
- break
- # This StackedObjectProxy may not have been registered by the
- # RegistryManager that was active when the exception was raised --
- # continue searching down the stack until it's found
- stack_level -= 1
- return context[stacked_id][1]
-
- def enable_restoration(self, stacked):
- """Replace the specified StackedObjectProxy's methods with their
- respective restoration versions.
-
- _current_obj_restoration forces recovery of the saved proxied object
- when a restoration context is active in the current thread.
-
- _push/pop_object_restoration avoid pushing/popping data
- (pushing/popping is only done at the Registry level) when a restoration
- context is active in the current thread"""
- if '_current_obj_orig' in stacked.__dict__:
- # Restoration already enabled
- return
-
- for func_name in ('_current_obj', '_push_object', '_pop_object'):
- orig_func = getattr(stacked, func_name)
- restoration_func = getattr(stacked, func_name + '_restoration')
- stacked.__dict__[func_name + '_orig'] = orig_func
- stacked.__dict__[func_name] = restoration_func
-
- def get_request_id(self, environ):
- """Return a unique identifier for the current request"""
- from paste.evalexception.middleware import get_debug_count
- return get_debug_count(environ)
-
- def restoration_begin(self, request_id):
- """Enable a restoration context in the current thread for the specified
- request_id"""
- if request_id in self.saved_registry_states:
- # Restore the old Registry object's state
- registry, reglist = self.saved_registry_states[request_id]
- registry.reglist = reglist
-
- self.restoration_context_id.request_id = request_id
-
- def restoration_end(self):
- """Register a restoration context as finished, if one exists"""
- try:
- del self.restoration_context_id.request_id
- except AttributeError:
- pass
-
- def in_restoration(self):
- """Determine if a restoration context is active for the current thread.
- Returns the request_id it's active for if so, otherwise False"""
- return getattr(self.restoration_context_id, 'request_id', False)
-
-restorer = StackedObjectRestorer()
-
-
-# Paste Deploy entry point
-def make_registry_manager(app, global_conf):
- return RegistryManager(app)
-
-make_registry_manager.__doc__ = RegistryManager.__doc__
diff --git a/lib/paste/reloader.py b/lib/paste/reloader.py
@@ -1,178 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-A file monitor and server restarter.
-
-Use this like:
-
-..code-block:: Python
-
- import reloader
- reloader.install()
-
-Then make sure your server is installed with a shell script like::
-
- err=3
- while test "$err" -eq 3 ; do
- python server.py
- err="$?"
- done
-
-or is run from this .bat file (if you use Windows)::
-
- @echo off
- :repeat
- python server.py
- if %errorlevel% == 3 goto repeat
-
-or run a monitoring process in Python (``paster serve --reload`` does
-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
-a dynamic set of files that grows over time you can use something like::
-
- def watch_config_files():
- return CONFIG_FILE_CACHE.keys()
- paste.reloader.add_file_callback(watch_config_files)
-
-Then every time the reloader polls files it will call
-``watch_config_files`` and check all the filenames it returns.
-"""
-
-import os
-import sys
-import time
-import threading
-import traceback
-from paste.util.classinstance import classinstancemethod
-
-def install(poll_interval=1):
- """
- Install the reloading monitor.
-
- On some platforms server threads may not terminate when the main
- thread does, causing ports to remain open/locked. The
- ``raise_keyboard_interrupt`` option creates a unignorable signal
- which causes the whole application to shut-down (rudely).
- """
- mon = Monitor(poll_interval=poll_interval)
- t = threading.Thread(target=mon.periodic_reload)
- t.setDaemon(True)
- t.start()
-
-class Monitor(object):
-
- instances = []
- global_extra_files = []
- global_file_callbacks = []
-
- def __init__(self, poll_interval):
- self.module_mtimes = {}
- self.keep_running = True
- self.poll_interval = poll_interval
- self.extra_files = list(self.global_extra_files)
- self.instances.append(self)
- self.file_callbacks = list(self.global_file_callbacks)
-
- def periodic_reload(self):
- while True:
- if not self.check_reload():
- # use os._exit() here and not sys.exit() since within a
- # thread sys.exit() just closes the given thread and
- # won't kill the process; note os._exit does not call
- # any atexit callbacks, nor does it do finally blocks,
- # flush open files, etc. In otherwords, it is rude.
- os._exit(3)
- break
- time.sleep(self.poll_interval)
-
- def check_reload(self):
- filenames = list(self.extra_files)
- for file_callback in self.file_callbacks:
- try:
- filenames.extend(file_callback())
- except:
- print >> sys.stderr, "Error calling paste.reloader callback %r:" % file_callback
- traceback.print_exc()
- for module in sys.modules.values():
- try:
- filename = module.__file__
- except (AttributeError, ImportError), exc:
- continue
- if filename is not None:
- filenames.append(filename)
- for filename in filenames:
- try:
- stat = os.stat(filename)
- if stat:
- mtime = stat.st_mtime
- else:
- mtime = 0
- except (OSError, IOError):
- continue
- if filename.endswith('.pyc') and os.path.exists(filename[:-1]):
- mtime = max(os.stat(filename[:-1]).st_mtime, mtime)
- elif filename.endswith('$py.class') and \
- os.path.exists(filename[:-9] + '.py'):
- mtime = max(os.stat(filename[:-9] + '.py').st_mtime, mtime)
- if not self.module_mtimes.has_key(filename):
- self.module_mtimes[filename] = mtime
- elif self.module_mtimes[filename] < mtime:
- print >> sys.stderr, (
- "%s changed; reloading..." % filename)
- return False
- return True
-
- def watch_file(self, cls, filename):
- """Watch the named file for changes"""
- filename = os.path.abspath(filename)
- if self is None:
- for instance in cls.instances:
- instance.watch_file(filename)
- cls.global_extra_files.append(filename)
- else:
- self.extra_files.append(filename)
-
- watch_file = classinstancemethod(watch_file)
-
- def add_file_callback(self, cls, callback):
- """Add a callback -- a function that takes no parameters -- that will
- return a list of filenames to watch for changes."""
- if self is None:
- for instance in cls.instances:
- instance.add_file_callback(callback)
- cls.global_file_callbacks.append(callback)
- else:
- self.file_callbacks.append(callback)
-
- add_file_callback = classinstancemethod(add_file_callback)
-
-if sys.platform.startswith('java'):
- try:
- from _systemrestart import SystemRestart
- except ImportError:
- pass
- else:
- class JythonMonitor(Monitor):
-
- """
- Monitor that utilizes Jython's special
- ``_systemrestart.SystemRestart`` exception.
-
- When raised from the main thread it causes Jython to reload
- the interpreter in the existing Java process (avoiding
- startup time).
-
- Note that this functionality of Jython is experimental and
- may change in the future.
- """
-
- def periodic_reload(self):
- while True:
- if not self.check_reload():
- raise SystemRestart()
- time.sleep(self.poll_interval)
-
-watch_file = Monitor.watch_file
-add_file_callback = Monitor.add_file_callback
diff --git a/lib/paste/request.py b/lib/paste/request.py
@@ -1,405 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# (c) 2005 Ian Bicking 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
-"""
-This module provides helper routines with work directly on a WSGI
-environment to solve common requirements.
-
- * get_cookies(environ)
- * parse_querystring(environ)
- * parse_formvars(environ, include_get_vars=True)
- * construct_url(environ, with_query_string=True, with_path_info=True,
- script_name=None, path_info=None, querystring=None)
- * path_info_split(path_info)
- * path_info_pop(environ)
- * resolve_relative_url(url, environ)
-
-"""
-import cgi
-from Cookie import SimpleCookie
-from StringIO import StringIO
-import urlparse
-import urllib
-try:
- from UserDict import DictMixin
-except ImportError:
- from paste.util.UserDict24 import DictMixin
-from paste.util.multidict import MultiDict
-
-__all__ = ['get_cookies', 'get_cookie_dict', 'parse_querystring',
- 'parse_formvars', 'construct_url', 'path_info_split',
- 'path_info_pop', 'resolve_relative_url', 'EnvironHeaders']
-
-def get_cookies(environ):
- """
- Gets a cookie object (which is a dictionary-like object) from the
- request environment; caches this value in case get_cookies is
- called again for the same request.
-
- """
- header = environ.get('HTTP_COOKIE', '')
- if environ.has_key('paste.cookies'):
- cookies, check_header = environ['paste.cookies']
- if check_header == header:
- return cookies
- cookies = SimpleCookie()
- cookies.load(header)
- environ['paste.cookies'] = (cookies, header)
- return cookies
-
-def get_cookie_dict(environ):
- """Return a *plain* dictionary of cookies as found in the request.
-
- Unlike ``get_cookies`` this returns a dictionary, not a
- ``SimpleCookie`` object. For incoming cookies a dictionary fully
- represents the information. Like ``get_cookies`` this caches and
- checks the cache.
- """
- header = environ.get('HTTP_COOKIE')
- if not header:
- return {}
- if environ.has_key('paste.cookies.dict'):
- cookies, check_header = environ['paste.cookies.dict']
- if check_header == header:
- return cookies
- cookies = SimpleCookie()
- cookies.load(header)
- result = {}
- for name in cookies:
- result[name] = cookies[name].value
- environ['paste.cookies.dict'] = (result, header)
- return result
-
-def parse_querystring(environ):
- """
- Parses a query string into a list like ``[(name, value)]``.
- Caches this value in case parse_querystring is called again
- for the same request.
-
- You can pass the result to ``dict()``, but be aware that keys that
- appear multiple times will be lost (only the last value will be
- preserved).
-
- """
- source = environ.get('QUERY_STRING', '')
- if not source:
- return []
- if 'paste.parsed_querystring' in environ:
- parsed, check_source = environ['paste.parsed_querystring']
- if check_source == source:
- return parsed
- parsed = cgi.parse_qsl(source, keep_blank_values=True,
- strict_parsing=False)
- environ['paste.parsed_querystring'] = (parsed, source)
- return parsed
-
-def parse_dict_querystring(environ):
- """Parses a query string like parse_querystring, but returns a MultiDict
-
- Caches this value in case parse_dict_querystring is called again
- for the same request.
-
- Example::
-
- >>> environ = {'QUERY_STRING': 'day=Monday&user=fred&user=jane'}
- >>> parsed = parse_dict_querystring(environ)
-
- >>> parsed['day']
- 'Monday'
- >>> parsed['user']
- 'fred'
- >>> parsed.getall('user')
- ['fred', 'jane']
-
- """
- source = environ.get('QUERY_STRING', '')
- if not source:
- return MultiDict()
- if 'paste.parsed_dict_querystring' in environ:
- parsed, check_source = environ['paste.parsed_dict_querystring']
- if check_source == source:
- return parsed
- parsed = cgi.parse_qsl(source, keep_blank_values=True,
- strict_parsing=False)
- multi = MultiDict(parsed)
- environ['paste.parsed_dict_querystring'] = (multi, source)
- return multi
-
-def parse_formvars(environ, include_get_vars=True):
- """Parses the request, returning a MultiDict of form variables.
-
- If ``include_get_vars`` is true then GET (query string) variables
- will also be folded into the MultiDict.
-
- All values should be strings, except for file uploads which are
- left as ``FieldStorage`` instances.
-
- If the request was not a normal form request (e.g., a POST with an
- XML body) then ``environ['wsgi.input']`` won't be read.
- """
- source = environ['wsgi.input']
- if 'paste.parsed_formvars' in environ:
- parsed, check_source = environ['paste.parsed_formvars']
- if check_source == source:
- if include_get_vars:
- parsed.update(parse_querystring(environ))
- return parsed
- # @@: Shouldn't bother FieldStorage parsing during GET/HEAD and
- # fake_out_cgi requests
- type = environ.get('CONTENT_TYPE', '').lower()
- if ';' in type:
- type = type.split(';', 1)[0]
- fake_out_cgi = type not in ('', 'application/x-www-form-urlencoded',
- 'multipart/form-data')
- # FieldStorage assumes a default CONTENT_LENGTH of -1, but a
- # default of 0 is better:
- if not environ.get('CONTENT_LENGTH'):
- environ['CONTENT_LENGTH'] = '0'
- # Prevent FieldStorage from parsing QUERY_STRING during GET/HEAD
- # requests
- old_query_string = environ.get('QUERY_STRING','')
- environ['QUERY_STRING'] = ''
- if fake_out_cgi:
- input = StringIO('')
- old_content_type = environ.get('CONTENT_TYPE')
- old_content_length = environ.get('CONTENT_LENGTH')
- environ['CONTENT_LENGTH'] = '0'
- environ['CONTENT_TYPE'] = ''
- else:
- input = environ['wsgi.input']
- fs = cgi.FieldStorage(fp=input,
- environ=environ,
- keep_blank_values=1)
- environ['QUERY_STRING'] = old_query_string
- if fake_out_cgi:
- environ['CONTENT_TYPE'] = old_content_type
- environ['CONTENT_LENGTH'] = old_content_length
- formvars = MultiDict()
- if isinstance(fs.value, list):
- for name in fs.keys():
- values = fs[name]
- if not isinstance(values, list):
- values = [values]
- for value in values:
- if not value.filename:
- value = value.value
- formvars.add(name, value)
- environ['paste.parsed_formvars'] = (formvars, source)
- if include_get_vars:
- formvars.update(parse_querystring(environ))
- return formvars
-
-def construct_url(environ, with_query_string=True, with_path_info=True,
- script_name=None, path_info=None, querystring=None):
- """Reconstructs the URL from the WSGI environment.
-
- You may override SCRIPT_NAME, PATH_INFO, and QUERYSTRING with
- the keyword arguments.
-
- """
- url = environ['wsgi.url_scheme']+'://'
-
- if environ.get('HTTP_HOST'):
- host = environ['HTTP_HOST']
- port = None
- if ':' in host:
- host, port = host.split(':', 1)
- if environ['wsgi.url_scheme'] == 'https':
- if port == '443':
- port = None
- elif environ['wsgi.url_scheme'] == 'http':
- if port == '80':
- port = None
- url += host
- if port:
- url += ':%s' % port
- else:
- url += environ['SERVER_NAME']
- if environ['wsgi.url_scheme'] == 'https':
- if environ['SERVER_PORT'] != '443':
- url += ':' + environ['SERVER_PORT']
- else:
- if environ['SERVER_PORT'] != '80':
- url += ':' + environ['SERVER_PORT']
-
- if script_name is None:
- url += urllib.quote(environ.get('SCRIPT_NAME',''))
- else:
- url += urllib.quote(script_name)
- if with_path_info:
- if path_info is None:
- url += urllib.quote(environ.get('PATH_INFO',''))
- else:
- url += urllib.quote(path_info)
- if with_query_string:
- if querystring is None:
- if environ.get('QUERY_STRING'):
- url += '?' + environ['QUERY_STRING']
- elif querystring:
- url += '?' + querystring
- return url
-
-def resolve_relative_url(url, environ):
- """
- Resolve the given relative URL as being relative to the
- location represented by the environment. This can be used
- for redirecting to a relative path. Note: if url is already
- absolute, this function will (intentionally) have no effect
- on it.
-
- """
- cur_url = construct_url(environ, with_query_string=False)
- return urlparse.urljoin(cur_url, url)
-
-def path_info_split(path_info):
- """
- Splits off the first segment of the path. Returns (first_part,
- rest_of_path). first_part can be None (if PATH_INFO is empty), ''
- (if PATH_INFO is '/'), or a name without any /'s. rest_of_path
- can be '' or a string starting with /.
-
- """
- if not path_info:
- return None, ''
- assert path_info.startswith('/'), (
- "PATH_INFO should start with /: %r" % path_info)
- path_info = path_info.lstrip('/')
- if '/' in path_info:
- first, rest = path_info.split('/', 1)
- return first, '/' + rest
- else:
- return path_info, ''
-
-def path_info_pop(environ):
- """
- 'Pops' off the next segment of PATH_INFO, pushing it onto
- SCRIPT_NAME, and returning that segment.
-
- For instance::
-
- >>> def call_it(script_name, path_info):
- ... env = {'SCRIPT_NAME': script_name, 'PATH_INFO': path_info}
- ... result = path_info_pop(env)
- ... print 'SCRIPT_NAME=%r; PATH_INFO=%r; returns=%r' % (
- ... env['SCRIPT_NAME'], env['PATH_INFO'], result)
- >>> call_it('/foo', '/bar')
- SCRIPT_NAME='/foo/bar'; PATH_INFO=''; returns='bar'
- >>> call_it('/foo/bar', '')
- SCRIPT_NAME='/foo/bar'; PATH_INFO=''; returns=None
- >>> call_it('/foo/bar', '/')
- SCRIPT_NAME='/foo/bar/'; PATH_INFO=''; returns=''
- >>> call_it('', '/1/2/3')
- SCRIPT_NAME='/1'; PATH_INFO='/2/3'; returns='1'
- >>> call_it('', '//1/2')
- SCRIPT_NAME='//1'; PATH_INFO='/2'; returns='1'
-
- """
- path = environ.get('PATH_INFO', '')
- if not path:
- return None
- while path.startswith('/'):
- environ['SCRIPT_NAME'] += '/'
- path = path[1:]
- if '/' not in path:
- environ['SCRIPT_NAME'] += path
- environ['PATH_INFO'] = ''
- return path
- else:
- segment, path = path.split('/', 1)
- environ['PATH_INFO'] = '/' + path
- environ['SCRIPT_NAME'] += segment
- return segment
-
-_parse_headers_special = {
- # This is a Zope convention, but we'll allow it here:
- 'HTTP_CGI_AUTHORIZATION': 'Authorization',
- 'CONTENT_LENGTH': 'Content-Length',
- 'CONTENT_TYPE': 'Content-Type',
- }
-
-def parse_headers(environ):
- """
- Parse the headers in the environment (like ``HTTP_HOST``) and
- yield a sequence of those (header_name, value) tuples.
- """
- # @@: Maybe should parse out comma-separated headers?
- for cgi_var, value in environ.iteritems():
- if cgi_var in _parse_headers_special:
- yield _parse_headers_special[cgi_var], value
- elif cgi_var.startswith('HTTP_'):
- yield cgi_var[5:].title().replace('_', '-'), value
-
-class EnvironHeaders(DictMixin):
- """An object that represents the headers as present in a
- WSGI environment.
-
- This object is a wrapper (with no internal state) for a WSGI
- request object, representing the CGI-style HTTP_* keys as a
- dictionary. Because a CGI environment can only hold one value for
- each key, this dictionary is single-valued (unlike outgoing
- headers).
- """
-
- def __init__(self, environ):
- self.environ = environ
-
- def _trans_name(self, name):
- key = 'HTTP_'+name.replace('-', '_').upper()
- if key == 'HTTP_CONTENT_LENGTH':
- key = 'CONTENT_LENGTH'
- elif key == 'HTTP_CONTENT_TYPE':
- key = 'CONTENT_TYPE'
- return key
-
- def _trans_key(self, key):
- if key == 'CONTENT_TYPE':
- return 'Content-Type'
- elif key == 'CONTENT_LENGTH':
- return 'Content-Length'
- elif key.startswith('HTTP_'):
- return key[5:].replace('_', '-').title()
- else:
- return None
-
- def __getitem__(self, item):
- return self.environ[self._trans_name(item)]
-
- def __setitem__(self, item, value):
- # @@: Should this dictionary be writable at all?
- self.environ[self._trans_name(item)] = value
-
- def __delitem__(self, item):
- del self.environ[self._trans_name(item)]
-
- def __iter__(self):
- for key in self.environ:
- name = self._trans_key(key)
- if name is not None:
- yield name
-
- def keys(self):
- return list(iter(self))
-
- def __contains__(self, item):
- return self._trans_name(item) in self.environ
-
-def _cgi_FieldStorage__repr__patch(self):
- """ monkey patch for FieldStorage.__repr__
-
- Unbelievely, the default __repr__ on FieldStorage reads
- the entire file content instead of being sane about it.
- This is a simple replacement that doesn't do that
- """
- if self.file:
- return "FieldStorage(%r, %r)" % (
- self.name, self.filename)
- return "FieldStorage(%r, %r, %r)" % (
- self.name, self.filename, self.value)
-
-cgi.FieldStorage.__repr__ = _cgi_FieldStorage__repr__patch
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/lib/paste/response.py b/lib/paste/response.py
@@ -1,240 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""Routines to generate WSGI responses"""
-
-############################################################
-## Headers
-############################################################
-import warnings
-
-class HeaderDict(dict):
-
- """
- This represents response headers. It handles the headers as a
- dictionary, with case-insensitive keys.
-
- Also there is an ``.add(key, value)`` method, which sets the key,
- or adds the value to the current value (turning it into a list if
- necessary).
-
- For passing to WSGI there is a ``.headeritems()`` method which is
- like ``.items()`` but unpacks value that are lists. It also
- handles encoding -- all headers are encoded in ASCII (if they are
- unicode).
-
- @@: Should that encoding be ISO-8859-1 or UTF-8? I'm not sure
- what the spec says.
- """
-
- def __getitem__(self, key):
- return dict.__getitem__(self, self.normalize(key))
-
- def __setitem__(self, key, value):
- dict.__setitem__(self, self.normalize(key), value)
-
- def __delitem__(self, key):
- dict.__delitem__(self, self.normalize(key))
-
- def __contains__(self, key):
- return dict.__contains__(self, self.normalize(key))
-
- has_key = __contains__
-
- def get(self, key, failobj=None):
- return dict.get(self, self.normalize(key), failobj)
-
- def setdefault(self, key, failobj=None):
- return dict.setdefault(self, self.normalize(key), failobj)
-
- def pop(self, key):
- return dict.pop(self, self.normalize(key))
-
- def update(self, other):
- for key in other:
- self[self.normalize(key)] = other[key]
-
- def normalize(self, key):
- return str(key).lower().strip()
-
- def add(self, key, value):
- key = self.normalize(key)
- if key in self:
- if isinstance(self[key], list):
- self[key].append(value)
- else:
- self[key] = [self[key], value]
- else:
- self[key] = value
-
- def headeritems(self):
- result = []
- for key, value in self.items():
- if isinstance(value, list):
- for v in value:
- result.append((key, str(v)))
- else:
- result.append((key, str(value)))
- return result
-
- #@classmethod
- def fromlist(cls, seq):
- self = cls()
- for name, value in seq:
- self.add(name, value)
- return self
-
- fromlist = classmethod(fromlist)
-
-def has_header(headers, name):
- """
- Is header named ``name`` present in headers?
- """
- name = name.lower()
- for header, value in headers:
- if header.lower() == name:
- return True
- return False
-
-def header_value(headers, name):
- """
- Returns the header's value, or None if no such header. If a
- header appears more than once, all the values of the headers
- are joined with ','. Note that this is consistent /w RFC 2616
- section 4.2 which states:
-
- It MUST be possible to combine the multiple header fields
- into one "field-name: field-value" pair, without changing
- the semantics of the message, by appending each subsequent
- field-value to the first, each separated by a comma.
-
- However, note that the original netscape usage of 'Set-Cookie',
- especially in MSIE which contains an 'expires' date will is not
- compatible with this particular concatination method.
- """
- name = name.lower()
- result = [value for header, value in headers
- if header.lower() == name]
- if result:
- return ','.join(result)
- else:
- return None
-
-def remove_header(headers, name):
- """
- Removes the named header from the list of headers. Returns the
- value of that header, or None if no header found. If multiple
- headers are found, only the last one is returned.
- """
- name = name.lower()
- i = 0
- result = None
- while i < len(headers):
- if headers[i][0].lower() == name:
- result = headers[i][1]
- del headers[i]
- continue
- i += 1
- return result
-
-def replace_header(headers, name, value):
- """
- Updates the headers replacing the first occurance of the given name
- with the value provided; asserting that no further occurances
- happen. Note that this is _not_ the same as remove_header and then
- append, as two distinct operations (del followed by an append) are
- not atomic in a threaded environment. Returns the previous header
- value for the provided name, if any. Clearly one should not use
- this function with ``set-cookie`` or other names that may have more
- than one occurance in the headers.
- """
- name = name.lower()
- i = 0
- result = None
- while i < len(headers):
- if headers[i][0].lower() == name:
- assert not result, "two values for the header '%s' found" % name
- result = headers[i][1]
- headers[i] = (name, value)
- i += 1
- if not result:
- headers.append((name, value))
- return result
-
-
-############################################################
-## Deprecated methods
-############################################################
-
-def error_body_response(error_code, message, __warn=True):
- """
- Returns a standard HTML response page for an HTTP error.
- **Note:** Deprecated
- """
- if __warn:
- warnings.warn(
- 'wsgilib.error_body_response is deprecated; use the '
- 'wsgi_application method on an HTTPException object '
- 'instead', DeprecationWarning, 2)
- return '''\
-<html>
- <head>
- <title>%(error_code)s</title>
- </head>
- <body>
- <h1>%(error_code)s</h1>
- %(message)s
- </body>
-</html>''' % {
- 'error_code': error_code,
- 'message': message,
- }
-
-
-def error_response(environ, error_code, message,
- debug_message=None, __warn=True):
- """
- Returns the status, headers, and body of an error response.
-
- Use like:
-
- .. code-block:: python
-
- status, headers, body = wsgilib.error_response(
- '301 Moved Permanently', 'Moved to <a href="%s">%s</a>'
- % (url, url))
- start_response(status, headers)
- return [body]
-
- **Note:** Deprecated
- """
- if __warn:
- warnings.warn(
- 'wsgilib.error_response is deprecated; use the '
- 'wsgi_application method on an HTTPException object '
- 'instead', DeprecationWarning, 2)
- if debug_message and environ.get('paste.config', {}).get('debug'):
- message += '\n\n<!-- %s -->' % debug_message
- body = error_body_response(error_code, message, __warn=False)
- headers = [('content-type', 'text/html'),
- ('content-length', str(len(body)))]
- return error_code, headers, body
-
-def error_response_app(error_code, message, debug_message=None,
- __warn=True):
- """
- An application that emits the given error response.
-
- **Note:** Deprecated
- """
- if __warn:
- warnings.warn(
- 'wsgilib.error_response_app is deprecated; use the '
- 'wsgi_application method on an HTTPException object '
- 'instead', DeprecationWarning, 2)
- def application(environ, start_response):
- status, headers, body = error_response(
- environ, error_code, message,
- debug_message=debug_message, __warn=False)
- start_response(status, headers)
- return [body]
- return application
diff --git a/lib/paste/session.py b/lib/paste/session.py
@@ -1,337 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-Creates a session object in your WSGI environment.
-
-Use like:
-
-..code-block:: Python
-
- environ['paste.session.factory']()
-
-This will return a dictionary. The contents of this dictionary will
-be saved to disk when the request is completed. The session will be
-created when you first fetch the session dictionary, and a cookie will
-be sent in that case. There's current no way to use sessions without
-cookies, and there's no way to delete a session except to clear its
-data.
-
-@@: This doesn't do any locking, and may cause problems when a single
-session is accessed concurrently. Also, it loads and saves the
-session for each request, with no caching. Also, sessions aren't
-expired.
-"""
-
-from Cookie import SimpleCookie
-import time
-import random
-import os
-import datetime
-import threading
-import tempfile
-
-try:
- import cPickle
-except ImportError:
- import pickle as cPickle
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
-from paste import wsgilib
-from paste import request
-
-class SessionMiddleware(object):
-
- def __init__(self, application, global_conf=None, **factory_kw):
- self.application = application
- self.factory_kw = factory_kw
-
- def __call__(self, environ, start_response):
- session_factory = SessionFactory(environ, **self.factory_kw)
- environ['paste.session.factory'] = session_factory
- remember_headers = []
-
- def session_start_response(status, headers, exc_info=None):
- if not session_factory.created:
- remember_headers[:] = [status, headers]
- return start_response(status, headers)
- headers.append(session_factory.set_cookie_header())
- return start_response(status, headers, exc_info)
-
- app_iter = self.application(environ, session_start_response)
- def start():
- if session_factory.created and remember_headers:
- # Tricky bastard used the session after start_response
- status, headers = remember_headers
- headers.append(session_factory.set_cookie_header())
- exc = ValueError(
- "You cannot get the session after content from the "
- "app_iter has been returned")
- start_response(status, headers, (exc.__class__, exc, None))
- def close():
- if session_factory.used:
- session_factory.close()
- return wsgilib.add_start_close(app_iter, start, close)
-
-
-class SessionFactory(object):
-
-
- def __init__(self, environ, cookie_name='_SID_',
- session_class=None,
- session_expiration=60*12, # in minutes
- **session_class_kw):
-
- self.created = False
- self.used = False
- self.environ = environ
- self.cookie_name = cookie_name
- self.session = None
- self.session_class = session_class or FileSession
- self.session_class_kw = session_class_kw
-
- self.expiration = session_expiration
-
- def __call__(self):
- self.used = True
- if self.session is not None:
- return self.session.data()
- cookies = request.get_cookies(self.environ)
- session = None
- if cookies.has_key(self.cookie_name):
- self.sid = cookies[self.cookie_name].value
- try:
- session = self.session_class(self.sid, create=False,
- **self.session_class_kw)
- except KeyError:
- # Invalid SID
- pass
- if session is None:
- self.created = True
- self.sid = self.make_sid()
- session = self.session_class(self.sid, create=True,
- **self.session_class_kw)
- session.clean_up()
- self.session = session
- return session.data()
-
- def has_session(self):
- if self.session is not None:
- return True
- cookies = request.get_cookies(self.environ)
- if cookies.has_key(self.cookie_name):
- return True
- return False
-
- def make_sid(self):
- # @@: need better algorithm
- return (''.join(['%02d' % x for x in time.localtime(time.time())[:6]])
- + '-' + self.unique_id())
-
- def unique_id(self, for_object=None):
- """
- Generates an opaque, identifier string that is practically
- guaranteed to be unique. If an object is passed, then its
- id() is incorporated into the generation. Relies on md5 and
- returns a 32 character long string.
- """
- r = [time.time(), random.random()]
- if hasattr(os, 'times'):
- r.append(os.times())
- if for_object is not None:
- r.append(id(for_object))
- md5_hash = md5(str(r))
- try:
- return md5_hash.hexdigest()
- except AttributeError:
- # Older versions of Python didn't have hexdigest, so we'll
- # do it manually
- hexdigest = []
- for char in md5_hash.digest():
- hexdigest.append('%02x' % ord(char))
- return ''.join(hexdigest)
-
- def set_cookie_header(self):
- c = SimpleCookie()
- c[self.cookie_name] = self.sid
- c[self.cookie_name]['path'] = '/'
-
- gmt_expiration_time = time.gmtime(time.time() + (self.expiration * 60))
- c[self.cookie_name]['expires'] = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", gmt_expiration_time)
-
- name, value = str(c).split(': ', 1)
- return (name, value)
-
- def close(self):
- if self.session is not None:
- self.session.close()
-
-
-last_cleanup = None
-cleaning_up = False
-cleanup_cycle = datetime.timedelta(seconds=15*60) #15 min
-
-class FileSession(object):
-
- def __init__(self, sid, create=False, session_file_path=tempfile.gettempdir(),
- chmod=None,
- expiration=2880, # in minutes: 48 hours
- ):
- if chmod and isinstance(chmod, basestring):
- chmod = int(chmod, 8)
- self.chmod = chmod
- if not sid:
- # Invalid...
- raise KeyError
- self.session_file_path = session_file_path
- self.sid = sid
- if not create:
- if not os.path.exists(self.filename()):
- raise KeyError
- self._data = None
-
- self.expiration = expiration
-
-
- def filename(self):
- return os.path.join(self.session_file_path, self.sid)
-
- def data(self):
- if self._data is not None:
- return self._data
- if os.path.exists(self.filename()):
- f = open(self.filename(), 'rb')
- self._data = cPickle.load(f)
- f.close()
- else:
- self._data = {}
- return self._data
-
- def close(self):
- if self._data is not None:
- filename = self.filename()
- exists = os.path.exists(filename)
- if not self._data:
- if exists:
- os.unlink(filename)
- else:
- f = open(filename, 'wb')
- cPickle.dump(self._data, f)
- f.close()
- if not exists and self.chmod:
- os.chmod(filename, self.chmod)
-
- def _clean_up(self):
- global cleaning_up
- try:
- exp_time = datetime.timedelta(seconds=self.expiration*60)
- now = datetime.datetime.now()
-
- #Open every session and check that it isn't too old
- for root, dirs, files in os.walk(self.session_file_path):
- for f in files:
- self._clean_up_file(f, exp_time=exp_time, now=now)
- finally:
- cleaning_up = False
-
- def _clean_up_file(self, f, exp_time, now):
- t = f.split("-")
- if len(t) != 2:
- return
- t = t[0]
- try:
- sess_time = datetime.datetime(
- int(t[0:4]),
- int(t[4:6]),
- int(t[6:8]),
- int(t[8:10]),
- int(t[10:12]),
- int(t[12:14]))
- except ValueError:
- # Probably not a session file at all
- return
-
- if sess_time + exp_time < now:
- os.remove(os.path.join(self.session_file_path, f))
-
- def clean_up(self):
- global last_cleanup, cleanup_cycle, cleaning_up
- now = datetime.datetime.now()
-
- if cleaning_up:
- return
-
- if not last_cleanup or last_cleanup + cleanup_cycle < now:
- if not cleaning_up:
- cleaning_up = True
- try:
- last_cleanup = now
- t = threading.Thread(target=self._clean_up)
- t.start()
- except:
- # Normally _clean_up should set cleaning_up
- # to false, but if something goes wrong starting
- # it...
- cleaning_up = False
- raise
-
-class _NoDefault(object):
- def __repr__(self):
- return '<dynamic default>'
-NoDefault = _NoDefault()
-
-def make_session_middleware(
- app, global_conf,
- session_expiration=NoDefault,
- expiration=NoDefault,
- cookie_name=NoDefault,
- session_file_path=NoDefault,
- chmod=NoDefault):
- """
- Adds a middleware that handles sessions for your applications.
- The session is a peristent dictionary. To get this dictionary
- in your application, use ``environ['paste.session.factory']()``
- which returns this persistent dictionary.
-
- Configuration:
-
- session_expiration:
- The time each session lives, in minutes. This controls
- the cookie expiration. Default 12 hours.
-
- expiration:
- The time each session lives on disk. Old sessions are
- culled from disk based on this. Default 48 hours.
-
- cookie_name:
- The cookie name used to track the session. Use different
- names to avoid session clashes.
-
- session_file_path:
- Sessions are put in this location, default /tmp.
-
- chmod:
- The octal chmod you want to apply to new sessions (e.g., 660
- to make the sessions group readable/writable)
-
- Each of these also takes from the global configuration. cookie_name
- and chmod take from session_cookie_name and session_chmod
- """
- if session_expiration is NoDefault:
- session_expiration = global_conf.get('session_expiration', 60*12)
- session_expiration = int(session_expiration)
- if expiration is NoDefault:
- expiration = global_conf.get('expiration', 60*48)
- expiration = int(expiration)
- if cookie_name is NoDefault:
- cookie_name = global_conf.get('session_cookie_name', '_SID_')
- if session_file_path is NoDefault:
- session_file_path = global_conf.get('session_file_path', '/tmp')
- if chmod is NoDefault:
- chmod = global_conf.get('session_chmod', None)
- return SessionMiddleware(
- app, session_expiration=session_expiration,
- expiration=expiration, cookie_name=cookie_name,
- session_file_path=session_file_path, chmod=chmod)
diff --git a/lib/paste/transaction.py b/lib/paste/transaction.py
@@ -1,120 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-"""
-Middleware related to transactions and database connections.
-
-At this time it is very basic; but will eventually sprout all that
-two-phase commit goodness that I don't need.
-
-.. note::
-
- This is experimental, and will change in the future.
-"""
-from paste.httpexceptions import HTTPException
-from wsgilib import catch_errors
-
-class TransactionManagerMiddleware(object):
-
- def __init__(self, application):
- self.application = application
-
- def __call__(self, environ, start_response):
- environ['paste.transaction_manager'] = manager = Manager()
- # This makes sure nothing else traps unexpected exceptions:
- environ['paste.throw_errors'] = True
- return catch_errors(self.application, environ, start_response,
- error_callback=manager.error,
- ok_callback=manager.finish)
-
-class Manager(object):
-
- def __init__(self):
- self.aborted = False
- self.transactions = []
-
- def abort(self):
- self.aborted = True
-
- def error(self, exc_info):
- self.aborted = True
- self.finish()
-
- def finish(self):
- for trans in self.transactions:
- if self.aborted:
- trans.rollback()
- else:
- trans.commit()
-
-
-class ConnectionFactory(object):
- """
- Provides a callable interface for connecting to ADBAPI databases in
- a WSGI style (using the environment). More advanced connection
- factories might use the REMOTE_USER and/or other environment
- variables to make the connection returned depend upon the request.
- """
- def __init__(self, module, *args, **kwargs):
- #assert getattr(module,'threadsaftey',0) > 0
- self.module = module
- self.args = args
- self.kwargs = kwargs
-
- # deal with database string quoting issues
- self.quote = lambda s: "'%s'" % s.replace("'","''")
- if hasattr(self.module,'PgQuoteString'):
- self.quote = self.module.PgQuoteString
-
- def __call__(self, environ=None):
- conn = self.module.connect(*self.args, **self.kwargs)
- conn.__dict__['module'] = self.module
- conn.__dict__['quote'] = self.quote
- return conn
-
-def BasicTransactionHandler(application, factory):
- """
- Provides a simple mechanism for starting a transaction based on the
- factory; and for either committing or rolling back the transaction
- depending on the result. It checks for the response's current
- status code either through the latest call to start_response; or
- through a HTTPException's code. If it is a 100, 200, or 300; the
- transaction is committed; otherwise it is rolled back.
- """
- def basic_transaction(environ, start_response):
- conn = factory(environ)
- environ['paste.connection'] = conn
- should_commit = [500]
- def finalizer(exc_info=None):
- if exc_info:
- if isinstance(exc_info[1], HTTPException):
- should_commit.append(exc_info[1].code)
- if should_commit.pop() < 400:
- conn.commit()
- else:
- try:
- conn.rollback()
- except:
- # TODO: check if rollback has already happened
- return
- conn.close()
- def basictrans_start_response(status, headers, exc_info = None):
- should_commit.append(int(status.split(" ")[0]))
- return start_response(status, headers, exc_info)
- return catch_errors(application, environ, basictrans_start_response,
- finalizer, finalizer)
- return basic_transaction
-
-__all__ = ['ConnectionFactory', 'BasicTransactionHandler']
-
-if '__main__' == __name__ and False:
- from pyPgSQL import PgSQL
- factory = ConnectionFactory(PgSQL, database="testing")
- conn = factory()
- curr = conn.cursor()
- curr.execute("SELECT now(), %s" % conn.quote("B'n\\'gles"))
- (time, bing) = curr.fetchone()
- print bing, time
-
diff --git a/lib/paste/translogger.py b/lib/paste/translogger.py
@@ -1,116 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Middleware for logging requests, using Apache combined log format
-"""
-
-import logging
-import time
-import urllib
-
-class TransLogger(object):
- """
- This logging middleware will log all requests as they go through.
- They are, by default, sent to a logger named ``'wsgi'`` at the
- INFO level.
-
- If ``setup_console_handler`` is true, then messages for the named
- logger will be sent to the console.
- """
-
- format = ('%(REMOTE_ADDR)s - %(REMOTE_USER)s [%(time)s] '
- '"%(REQUEST_METHOD)s %(REQUEST_URI)s %(HTTP_VERSION)s" '
- '%(status)s %(bytes)s "%(HTTP_REFERER)s" "%(HTTP_USER_AGENT)s"')
-
- def __init__(self, application,
- logger=None,
- format=None,
- logging_level=logging.INFO,
- logger_name='wsgi',
- setup_console_handler=True,
- set_logger_level=logging.DEBUG):
- if format is not None:
- self.format = format
- self.application = application
- self.logging_level = logging_level
- self.logger_name = logger_name
- if logger is None:
- self.logger = logging.getLogger(self.logger_name)
- if setup_console_handler:
- console = logging.StreamHandler()
- console.setLevel(logging.DEBUG)
- # We need to control the exact format:
- console.setFormatter(logging.Formatter('%(message)s'))
- self.logger.addHandler(console)
- self.logger.propagate = False
- if set_logger_level is not None:
- self.logger.setLevel(set_logger_level)
- else:
- self.logger = logger
-
- def __call__(self, environ, start_response):
- start = time.localtime()
- req_uri = urllib.quote(environ.get('SCRIPT_NAME', '')
- + environ.get('PATH_INFO', ''))
- if environ.get('QUERY_STRING'):
- req_uri += '?'+environ['QUERY_STRING']
- method = environ['REQUEST_METHOD']
- def replacement_start_response(status, headers, exc_info=None):
- # @@: Ideally we would count the bytes going by if no
- # content-length header was provided; but that does add
- # some overhead, so at least for now we'll be lazy.
- bytes = None
- for name, value in headers:
- if name.lower() == 'content-length':
- bytes = value
- self.write_log(environ, method, req_uri, start, status, bytes)
- return start_response(status, headers)
- return self.application(environ, replacement_start_response)
-
- def write_log(self, environ, method, req_uri, start, status, bytes):
- if bytes is None:
- bytes = '-'
- if time.daylight:
- offset = time.altzone / 60 / 60 * -100
- else:
- offset = time.timezone / 60 / 60 * -100
- if offset >= 0:
- offset = "+%0.4d" % (offset)
- elif offset < 0:
- offset = "%0.4d" % (offset)
- d = {
- 'REMOTE_ADDR': environ.get('REMOTE_ADDR') or '-',
- 'REMOTE_USER': environ.get('REMOTE_USER') or '-',
- 'REQUEST_METHOD': method,
- 'REQUEST_URI': req_uri,
- 'HTTP_VERSION': environ.get('SERVER_PROTOCOL'),
- 'time': time.strftime('%d/%b/%Y:%H:%M:%S ', start) + offset,
- 'status': status.split(None, 1)[0],
- 'bytes': bytes,
- 'HTTP_REFERER': environ.get('HTTP_REFERER', '-'),
- 'HTTP_USER_AGENT': environ.get('HTTP_USER_AGENT', '-'),
- }
- message = self.format % d
- self.logger.log(self.logging_level, message)
-
-def make_filter(
- app, global_conf,
- logger_name='wsgi',
- format=None,
- logging_level=logging.INFO,
- setup_console_handler=True,
- set_logger_level=logging.DEBUG):
- from paste.util.converters import asbool
- if isinstance(logging_level, basestring):
- logging_level = logging._levelNames[logging_level]
- if isinstance(set_logger_level, basestring):
- set_logger_level = logging._levelNames[set_logger_level]
- return TransLogger(
- app,
- format=format or None,
- logging_level=logging_level,
- logger_name=logger_name,
- setup_console_handler=asbool(setup_console_handler),
- set_logger_level=set_logger_level)
-
-make_filter.__doc__ = TransLogger.__doc__
diff --git a/lib/paste/url.py b/lib/paste/url.py
@@ -1,475 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-This module implements a class for handling URLs.
-"""
-import urllib
-import cgi
-from paste import request
-# Imported lazily from FormEncode:
-variabledecode = None
-
-__all__ = ["URL", "Image"]
-
-def html_quote(v):
- if v is None:
- return ''
- return cgi.escape(str(v), 1)
-
-def url_quote(v):
- if v is None:
- return ''
- return urllib.quote(str(v))
-
-url_unquote = urllib.unquote
-
-def js_repr(v):
- if v is None:
- return 'null'
- elif v is False:
- return 'false'
- elif v is True:
- return 'true'
- elif isinstance(v, list):
- return '[%s]' % ', '.join(map(js_repr, v))
- elif isinstance(v, dict):
- return '{%s}' % ', '.join(
- ['%s: %s' % (js_repr(key), js_repr(value))
- for key, value in v])
- elif isinstance(v, str):
- return repr(v)
- elif isinstance(v, unicode):
- # @@: how do you do Unicode literals in Javascript?
- return repr(v.encode('UTF-8'))
- elif isinstance(v, (float, int)):
- return repr(v)
- elif isinstance(v, long):
- return repr(v).lstrip('L')
- elif hasattr(v, '__js_repr__'):
- return v.__js_repr__()
- else:
- raise ValueError(
- "I don't know how to turn %r into a Javascript representation"
- % v)
-
-class URLResource(object):
-
- """
- This is an abstract superclass for different kinds of URLs
- """
-
- default_params = {}
-
- def __init__(self, url, vars=None, attrs=None,
- params=None):
- self.url = url or '/'
- self.vars = vars or []
- self.attrs = attrs or {}
- self.params = self.default_params.copy()
- self.original_params = params or {}
- if params:
- self.params.update(params)
-
- #@classmethod
- def from_environ(cls, environ, with_query_string=True,
- with_path_info=True, script_name=None,
- path_info=None, querystring=None):
- url = request.construct_url(
- environ, with_query_string=False,
- with_path_info=with_path_info, script_name=script_name,
- path_info=path_info)
- if with_query_string:
- if querystring is None:
- vars = request.parse_querystring(environ)
- else:
- vars = cgi.parse_qsl(
- querystring,
- keep_blank_values=True,
- strict_parsing=False)
- else:
- vars = None
- v = cls(url, vars=vars)
- return v
-
- from_environ = classmethod(from_environ)
-
- def __call__(self, *args, **kw):
- res = self._add_positional(args)
- res = res._add_vars(kw)
- return res
-
- def __getitem__(self, item):
- if '=' in item:
- name, value = item.split('=', 1)
- return self._add_vars({url_unquote(name): url_unquote(value)})
- return self._add_positional((item,))
-
- def attr(self, **kw):
- for key in kw.keys():
- if key.endswith('_'):
- kw[key[:-1]] = kw[key]
- del kw[key]
- new_attrs = self.attrs.copy()
- new_attrs.update(kw)
- return self.__class__(self.url, vars=self.vars,
- attrs=new_attrs,
- params=self.original_params)
-
- def param(self, **kw):
- new_params = self.original_params.copy()
- new_params.update(kw)
- return self.__class__(self.url, vars=self.vars,
- attrs=self.attrs,
- params=new_params)
-
- def coerce_vars(self, vars):
- global variabledecode
- need_variable_encode = False
- for key, value in vars.items():
- if isinstance(value, dict):
- need_variable_encode = True
- if key.endswith('_'):
- vars[key[:-1]] = vars[key]
- del vars[key]
- if need_variable_encode:
- if variabledecode is None:
- from formencode import variabledecode
- vars = variabledecode.variable_encode(vars)
- return vars
-
-
- def var(self, **kw):
- kw = self.coerce_vars(kw)
- new_vars = self.vars + kw.items()
- return self.__class__(self.url, vars=new_vars,
- attrs=self.attrs,
- params=self.original_params)
-
- def setvar(self, **kw):
- """
- Like ``.var(...)``, except overwrites keys, where .var simply
- extends the keys. Setting a variable to None here will
- effectively delete it.
- """
- kw = self.coerce_vars(kw)
- new_vars = []
- for name, values in self.vars:
- if name in kw:
- continue
- new_vars.append((name, values))
- new_vars.extend(kw.items())
- return self.__class__(self.url, vars=new_vars,
- attrs=self.attrs,
- params=self.original_params)
-
- def setvars(self, **kw):
- """
- Creates a copy of this URL, but with all the variables set/reset
- (like .setvar(), except clears past variables at the same time)
- """
- return self.__class__(self.url, vars=kw.items(),
- attrs=self.attrs,
- params=self.original_params)
-
- def addpath(self, *paths):
- u = self
- for path in paths:
- path = str(path).lstrip('/')
- new_url = u.url
- if not new_url.endswith('/'):
- new_url += '/'
- u = u.__class__(new_url+path, vars=u.vars,
- attrs=u.attrs,
- params=u.original_params)
- return u
-
- __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:
- s += '?'
- vars = []
- for name, val in self.vars:
- if isinstance(val, (list, tuple)):
- val = [v for v in val if v is not None]
- elif val is None:
- continue
- vars.append((name, val))
- s += urllib.urlencode(vars, True)
- return s
-
- href = property(href__get)
-
- def __repr__(self):
- base = '<%s %s' % (self.__class__.__name__,
- self.href or "''")
- if self.attrs:
- base += ' attrs(%s)' % (
- ' '.join(['%s="%s"' % (html_quote(n), html_quote(v))
- for n, v in self.attrs.items()]))
- if self.original_params:
- base += ' params(%s)' % (
- ', '.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(
- "You cannot get the HTML of %r until you set the "
- "'tag' param'" % self)
- content = self._get_content()
- tag = '<%s' % self.params.get('tag')
- attrs = ' '.join([
- '%s="%s"' % (html_quote(n), html_quote(v))
- for n, v in self._html_attrs()])
- if attrs:
- tag += ' ' + attrs
- tag += self._html_extra()
- if content is None:
- return tag + ' />'
- else:
- return '%s>%s</%s>' % (tag, content, self.params.get('tag'))
-
- html = property(html__get)
-
- def _html_attrs(self):
- return self.attrs.items()
-
- def _html_extra(self):
- return ''
-
- def _get_content(self):
- """
- Return the content for a tag (for self.html); return None
- for an empty tag (like ``<img />``)
- """
- raise NotImplementedError
-
- def _add_vars(self, vars):
- raise NotImplementedError
-
- def _add_positional(self, args):
- raise NotImplementedError
-
-class URL(URLResource):
-
- r"""
- >>> u = URL('http://localhost')
- >>> u
- <URL http://localhost>
- >>> u = u['view']
- >>> str(u)
- 'http://localhost/view'
- >>> u['//foo'].param(content='view').html
- '<a href="http://localhost/view/foo">view</a>'
- >>> u.param(confirm='Really?', content='goto').html
- '<a href="http://localhost/view" onclick="return confirm(\'Really?\')">goto</a>'
- >>> u(title='See "it"', content='goto').html
- '<a href="http://localhost/view?title=See+%22it%22">goto</a>'
- >>> u('another', var='fuggetaboutit', content='goto').html
- '<a href="http://localhost/view/another?var=fuggetaboutit">goto</a>'
- >>> u.attr(content='goto').html
- Traceback (most recent call last):
- ....
- ValueError: You must give a content param to <URL http://localhost/view attrs(content="goto")> generate anchor tags
- >>> str(u['foo=bar%20stuff'])
- 'http://localhost/view?foo=bar+stuff'
- """
-
- default_params = {'tag': 'a'}
-
- def __str__(self):
- return self.href
-
- def _get_content(self):
- if not self.params.get('content'):
- raise ValueError(
- "You must give a content param to %r generate anchor tags"
- % self)
- return self.params['content']
-
- def _add_vars(self, vars):
- url = self
- for name in ('confirm', 'content'):
- if name in vars:
- url = url.param(**{name: vars.pop(name)})
- if 'target' in vars:
- url = url.attr(target=vars.pop('target'))
- return url.var(**vars)
-
- def _add_positional(self, args):
- return self.addpath(*args)
-
- def _html_attrs(self):
- attrs = self.attrs.items()
- attrs.insert(0, ('href', self.href))
- if self.params.get('confirm'):
- attrs.append(('onclick', 'return confirm(%s)'
- % js_repr(self.params['confirm'])))
- return attrs
-
- def onclick_goto__get(self):
- return 'location.href=%s; return false' % js_repr(self.href)
-
- onclick_goto = property(onclick_goto__get)
-
- def button__get(self):
- return self.become(Button)
-
- button = property(button__get)
-
- def js_popup__get(self):
- return self.become(JSPopup)
-
- js_popup = property(js_popup__get)
-
-class Image(URLResource):
-
- r"""
- >>> i = Image('/images')
- >>> i = i / '/foo.png'
- >>> i.html
- '<img src="/images/foo.png" />'
- >>> str(i['alt=foo'])
- '<img src="/images/foo.png" alt="foo" />'
- >>> i.href
- '/images/foo.png'
- """
-
- default_params = {'tag': 'img'}
-
- def __str__(self):
- return self.html
-
- def _get_content(self):
- return None
-
- def _add_vars(self, vars):
- return self.attr(**vars)
-
- def _add_positional(self, args):
- return self.addpath(*args)
-
- def _html_attrs(self):
- attrs = self.attrs.items()
- attrs.insert(0, ('src', self.href))
- return attrs
-
-class Button(URLResource):
-
- r"""
- >>> u = URL('/')
- >>> u = u / 'delete'
- >>> b = u.button['confirm=Sure?'](id=5, content='del')
- >>> str(b)
- '<button onclick="if (confirm(\'Sure?\')) {location.href=\'/delete?id=5\'}; return false">del</button>'
- """
-
- default_params = {'tag': 'button'}
-
- def __str__(self):
- return self.html
-
- def _get_content(self):
- if self.params.get('content'):
- return self.params['content']
- if self.attrs.get('value'):
- return self.attrs['content']
- # @@: Error?
- return None
-
- def _add_vars(self, vars):
- button = self
- if 'confirm' in vars:
- button = button.param(confirm=vars.pop('confirm'))
- if 'content' in vars:
- button = button.param(content=vars.pop('content'))
- return button.var(**vars)
-
- def _add_positional(self, args):
- return self.addpath(*args)
-
- def _html_attrs(self):
- attrs = self.attrs.items()
- onclick = 'location.href=%s' % js_repr(self.href)
- if self.params.get('confirm'):
- onclick = 'if (confirm(%s)) {%s}' % (
- js_repr(self.params['confirm']), onclick)
- onclick += '; return false'
- attrs.insert(0, ('onclick', onclick))
- return attrs
-
-class JSPopup(URLResource):
-
- r"""
- >>> u = URL('/')
- >>> u = u / 'view'
- >>> j = u.js_popup(content='view')
- >>> j.html
- '<a href="/view" onclick="window.open(\'/view\', \'_blank\'); return false" target="_blank">view</a>'
- """
-
- default_params = {'tag': 'a', 'target': '_blank'}
-
- def _add_vars(self, vars):
- button = self
- for var in ('width', 'height', 'stripped', 'content'):
- if var in vars:
- button = button.param(**{var: vars.pop(var)})
- return button.var(**vars)
-
- def _window_args(self):
- p = self.params
- features = []
- if p.get('stripped'):
- p['location'] = p['status'] = p['toolbar'] = '0'
- for param in 'channelmode directories fullscreen location menubar resizable scrollbars status titlebar'.split():
- if param not in p:
- continue
- v = p[param]
- if v not in ('yes', 'no', '1', '0'):
- if v:
- v = '1'
- else:
- v = '0'
- features.append('%s=%s' % (param, v))
- for param in 'height left top width':
- if not p.get(param):
- continue
- features.append('%s=%s' % (param, p[param]))
- args = [self.href, p['target']]
- if features:
- args.append(','.join(features))
- return ', '.join(map(js_repr, args))
-
- def _html_attrs(self):
- attrs = self.attrs.items()
- onclick = ('window.open(%s); return false'
- % self._window_args())
- attrs.insert(0, ('target', self.params['target']))
- attrs.insert(0, ('onclick', onclick))
- attrs.insert(0, ('href', self.href))
- return attrs
-
- def _get_content(self):
- if not self.params.get('content'):
- raise ValueError(
- "You must give a content param to %r generate anchor tags"
- % self)
- return self.params['content']
-
- def _add_positional(self, args):
- return self.addpath(*args)
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
-
diff --git a/lib/paste/urlmap.py b/lib/paste/urlmap.py
@@ -1,251 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Map URL prefixes to WSGI applications. See ``URLMap``
-"""
-
-from UserDict import DictMixin
-import re
-import os
-from paste import httpexceptions
-
-__all__ = ['URLMap', 'PathProxyURLMap']
-
-def urlmap_factory(loader, global_conf, **local_conf):
- if 'not_found_app' in local_conf:
- not_found_app = local_conf.pop('not_found_app')
- else:
- not_found_app = global_conf.get('not_found_app')
- if not_found_app:
- not_found_app = loader.get_app(not_found_app, global_conf=global_conf)
- urlmap = URLMap(not_found_app=not_found_app)
- for path, app_name in local_conf.items():
- path = parse_path_expression(path)
- app = loader.get_app(app_name, global_conf=global_conf)
- urlmap[path] = app
- return urlmap
-
-def parse_path_expression(path):
- """
- Parses a path expression like 'domain foobar.com port 20 /' or
- just '/foobar' for a path alone. Returns as an address that
- URLMap likes.
- """
- parts = path.split()
- domain = port = path = None
- while parts:
- if parts[0] == 'domain':
- parts.pop(0)
- if not parts:
- raise ValueError("'domain' must be followed with a domain name")
- if domain:
- raise ValueError("'domain' given twice")
- domain = parts.pop(0)
- elif parts[0] == 'port':
- parts.pop(0)
- if not parts:
- raise ValueError("'port' must be followed with a port number")
- if port:
- raise ValueError("'port' given twice")
- port = parts.pop(0)
- else:
- if path:
- raise ValueError("more than one path given (have %r, got %r)"
- % (path, parts[0]))
- path = parts.pop(0)
- s = ''
- if domain:
- s = 'http://%s' % domain
- if port:
- if not domain:
- raise ValueError("If you give a port, you must also give a domain")
- s += ':' + port
- if path:
- if s:
- s += '/'
- s += path
- return s
-
-class URLMap(DictMixin):
-
- """
- URLMap instances are dictionary-like object that dispatch to one
- of several applications based on the URL.
-
- The dictionary keys are URLs to match (like
- ``PATH_INFO.startswith(url)``), and the values are applications to
- dispatch to. URLs are matched most-specific-first, i.e., longest
- URL first. The ``SCRIPT_NAME`` and ``PATH_INFO`` environmental
- variables are adjusted to indicate the new context.
-
- URLs can also include domains, like ``http://blah.com/foo``, or as
- tuples ``('blah.com', '/foo')``. This will match domain names; without
- the ``http://domain`` or with a domain of ``None`` any domain will be
- matched (so long as no other explicit domain matches). """
-
- def __init__(self, not_found_app=None):
- self.applications = []
- if not not_found_app:
- not_found_app = self.not_found_app
- self.not_found_application = not_found_app
-
- norm_url_re = re.compile('//+')
- domain_url_re = re.compile('^(http|https)://')
-
- def not_found_app(self, environ, start_response):
- mapper = environ.get('paste.urlmap_object')
- if mapper:
- matches = [p for p, a in mapper.applications]
- extra = 'defined apps: %s' % (
- ',\n '.join(map(repr, matches)))
- else:
- extra = ''
- extra += '\nSCRIPT_NAME: %r' % environ.get('SCRIPT_NAME')
- extra += '\nPATH_INFO: %r' % environ.get('PATH_INFO')
- extra += '\nHTTP_HOST: %r' % environ.get('HTTP_HOST')
- app = httpexceptions.HTTPNotFound(
- environ['PATH_INFO'],
- comment=extra).wsgi_application
- return app(environ, start_response)
-
- def normalize_url(self, url, trim=True):
- if isinstance(url, (list, tuple)):
- domain = url[0]
- url = self.normalize_url(url[1])[1]
- return domain, url
- assert (not url or url.startswith('/')
- or self.domain_url_re.search(url)), (
- "URL fragments must start with / or http:// (you gave %r)" % url)
- match = self.domain_url_re.search(url)
- if match:
- url = url[match.end():]
- if '/' in url:
- domain, url = url.split('/', 1)
- url = '/' + url
- else:
- domain, url = url, ''
- else:
- domain = None
- url = self.norm_url_re.sub('/', url)
- if trim:
- url = url.rstrip('/')
- return domain, url
-
- def sort_apps(self):
- """
- Make sure applications are sorted with longest URLs first
- """
- def key(app_desc):
- (domain, url), app = app_desc
- if not domain:
- # Make sure empty domains sort last:
- return '\xff', -len(url)
- else:
- return domain, -len(url)
- apps = [(key(desc), desc) for desc in self.applications]
- apps.sort()
- self.applications = [desc for (sortable, desc) in apps]
-
- def __setitem__(self, url, app):
- if app is None:
- try:
- del self[url]
- except KeyError:
- pass
- return
- dom_url = self.normalize_url(url)
- if dom_url in self:
- del self[dom_url]
- self.applications.append((dom_url, app))
- self.sort_apps()
-
- def __getitem__(self, url):
- dom_url = self.normalize_url(url)
- for app_url, app in self.applications:
- if app_url == dom_url:
- return app
- raise KeyError(
- "No application with the url %r (domain: %r; existing: %s)"
- % (url[1], url[0] or '*', self.applications))
-
- def __delitem__(self, url):
- url = self.normalize_url(url)
- for app_url, app in self.applications:
- if app_url == url:
- self.applications.remove((app_url, app))
- break
- else:
- raise KeyError(
- "No application with the url %r" % (url,))
-
- def keys(self):
- return [app_url for app_url, app in self.applications]
-
- def __call__(self, environ, start_response):
- host = environ.get('HTTP_HOST', environ.get('SERVER_NAME')).lower()
- if ':' in host:
- host, port = host.split(':', 1)
- else:
- if environ['wsgi.url_scheme'] == 'http':
- port = '80'
- else:
- port = '443'
- path_info = environ.get('PATH_INFO')
- path_info = self.normalize_url(path_info, False)[1]
- for (domain, app_url), app in self.applications:
- if domain and domain != host and domain != host+':'+port:
- continue
- if (path_info == app_url
- or path_info.startswith(app_url + '/')):
- environ['SCRIPT_NAME'] += app_url
- environ['PATH_INFO'] = path_info[len(app_url):]
- return app(environ, start_response)
- environ['paste.urlmap_object'] = self
- return self.not_found_application(environ, start_response)
-
-
-class PathProxyURLMap(object):
-
- """
- This is a wrapper for URLMap that catches any strings that
- are passed in as applications; these strings are treated as
- filenames (relative to `base_path`) and are passed to the
- callable `builder`, which will return an application.
-
- This is intended for cases when configuration files can be
- treated as applications.
-
- `base_paste_url` is the URL under which all applications added through
- this wrapper must go. Use ``""`` if you want this to not
- change incoming URLs.
- """
-
- def __init__(self, map, base_paste_url, base_path, builder):
- self.map = map
- self.base_paste_url = self.map.normalize_url(base_paste_url)
- self.base_path = base_path
- self.builder = builder
-
- def __setitem__(self, url, app):
- if isinstance(app, (str, unicode)):
- app_fn = os.path.join(self.base_path, app)
- app = self.builder(app_fn)
- url = self.map.normalize_url(url)
- # @@: This means http://foo.com/bar will potentially
- # match foo.com, but /base_paste_url/bar, which is unintuitive
- url = (url[0] or self.base_paste_url[0],
- self.base_paste_url[1] + url[1])
- self.map[url] = app
-
- def __getattr__(self, attr):
- return getattr(self.map, attr)
-
- # This is really the only settable attribute
- def not_found_application__get(self):
- return self.map.not_found_application
- def not_found_application__set(self, value):
- self.map.not_found_application = value
- not_found_application = property(not_found_application__get,
- not_found_application__set)
-
-
diff --git a/lib/paste/urlparser.py b/lib/paste/urlparser.py
@@ -1,652 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-WSGI applications that parse the URL and dispatch to on-disk resources
-"""
-
-import os
-import sys
-import imp
-import mimetypes
-try:
- import pkg_resources
-except ImportError:
- pkg_resources = None
-from paste import request
-from paste import fileapp
-from paste.util import import_string
-from paste import httpexceptions
-from httpheaders import ETAG
-from paste.util import converters
-
-class NoDefault(object):
- pass
-
-__all__ = ['URLParser', 'StaticURLParser', 'PkgResourcesParser']
-
-class URLParser(object):
-
- """
- WSGI middleware
-
- Application dispatching, based on URL. An instance of `URLParser` is
- an application that loads and delegates to other applications. It
- looks for files in its directory that match the first part of
- PATH_INFO; these may have an extension, but are not required to have
- one, in which case the available files are searched to find the
- appropriate file. If it is ambiguous, a 404 is returned and an error
- logged.
-
- By default there is a constructor for .py files that loads the module,
- and looks for an attribute ``application``, which is a ready
- application object, or an attribute that matches the module name,
- which is a factory for building applications, and is called with no
- arguments.
-
- URLParser will also look in __init__.py for special overrides.
- These overrides are:
-
- ``urlparser_hook(environ)``
- This can modify the environment. Its return value is ignored,
- and it cannot be used to change the response in any way. You
- *can* use this, for example, to manipulate SCRIPT_NAME/PATH_INFO
- (try to keep them consistent with the original URL -- but
- consuming PATH_INFO and moving that to SCRIPT_NAME is ok).
-
- ``urlparser_wrap(environ, start_response, app)``:
- After URLParser finds the application, it calls this function
- (if present). If this function doesn't call
- ``app(environ, start_response)`` then the application won't be
- called at all! This can be used to allocate resources (with
- ``try:finally:``) or otherwise filter the output of the
- application.
-
- ``not_found_hook(environ, start_response)``:
- If no file can be found (*in this directory*) to match the
- request, then this WSGI application will be called. You can
- use this to change the URL and pass the request back to
- URLParser again, or on to some other application. This
- doesn't catch all ``404 Not Found`` responses, just missing
- files.
-
- ``application(environ, start_response)``:
- This basically overrides URLParser completely, and the given
- application is used for all requests. ``urlparser_wrap`` and
- ``urlparser_hook`` are still called, but the filesystem isn't
- searched in any way.
- """
-
- parsers_by_directory = {}
-
- # This is lazily initialized
- init_module = NoDefault
-
- global_constructors = {}
-
- def __init__(self, global_conf,
- directory, base_python_name,
- index_names=NoDefault,
- hide_extensions=NoDefault,
- ignore_extensions=NoDefault,
- constructors=None,
- **constructor_conf):
- """
- Create a URLParser object that looks at `directory`.
- `base_python_name` is the package that this directory
- represents, thus any Python modules in this directory will
- be given names under this package.
- """
- if global_conf:
- import warnings
- warnings.warn(
- 'The global_conf argument to URLParser is deprecated; '
- 'either pass in None or {}, or use make_url_parser',
- DeprecationWarning)
- else:
- global_conf = {}
- if os.path.sep != '/':
- directory = directory.replace(os.path.sep, '/')
- self.directory = directory
- self.base_python_name = base_python_name
- # This logic here should be deprecated since it is in
- # make_url_parser
- if index_names is NoDefault:
- index_names = global_conf.get(
- 'index_names', ('index', 'Index', 'main', 'Main'))
- self.index_names = converters.aslist(index_names)
- if hide_extensions is NoDefault:
- hide_extensions = global_conf.get(
- 'hide_extensions', ('.pyc', '.bak', '.py~', '.pyo'))
- self.hide_extensions = converters.aslist(hide_extensions)
- if ignore_extensions is NoDefault:
- ignore_extensions = global_conf.get(
- 'ignore_extensions', ())
- self.ignore_extensions = converters.aslist(ignore_extensions)
- self.constructors = self.global_constructors.copy()
- if constructors:
- self.constructors.update(constructors)
- # @@: Should we also check the global options for constructors?
- for name, value in constructor_conf.items():
- if not name.startswith('constructor '):
- raise ValueError(
- "Only extra configuration keys allowed are "
- "'constructor .ext = import_expr'; you gave %r "
- "(=%r)" % (name, value))
- ext = name[len('constructor '):].strip()
- if isinstance(value, (str, unicode)):
- value = import_string.eval_import(value)
- self.constructors[ext] = value
-
- def __call__(self, environ, start_response):
- environ['paste.urlparser.base_python_name'] = self.base_python_name
- if self.init_module is NoDefault:
- self.init_module = self.find_init_module(environ)
- path_info = environ.get('PATH_INFO', '')
- if not path_info:
- return self.add_slash(environ, start_response)
- if (self.init_module
- and getattr(self.init_module, 'urlparser_hook', None)):
- self.init_module.urlparser_hook(environ)
- orig_path_info = environ['PATH_INFO']
- orig_script_name = environ['SCRIPT_NAME']
- application, filename = self.find_application(environ)
- if not application:
- if (self.init_module
- and getattr(self.init_module, 'not_found_hook', None)
- and environ.get('paste.urlparser.not_found_parser') is not self):
- not_found_hook = self.init_module.not_found_hook
- environ['paste.urlparser.not_found_parser'] = self
- environ['PATH_INFO'] = orig_path_info
- environ['SCRIPT_NAME'] = orig_script_name
- return not_found_hook(environ, start_response)
- if filename is None:
- name, rest_of_path = request.path_info_split(environ['PATH_INFO'])
- if not name:
- name = 'one of %s' % ', '.join(
- self.index_names or
- ['(no index_names defined)'])
-
- return self.not_found(
- environ, start_response,
- 'Tried to load %s from directory %s'
- % (name, self.directory))
- else:
- environ['wsgi.errors'].write(
- 'Found resource %s, but could not construct application\n'
- % filename)
- return self.not_found(
- environ, start_response,
- 'Tried to load %s from directory %s'
- % (filename, self.directory))
- if (self.init_module
- and getattr(self.init_module, 'urlparser_wrap', None)):
- return self.init_module.urlparser_wrap(
- environ, start_response, application)
- else:
- return application(environ, start_response)
-
- def find_application(self, environ):
- if (self.init_module
- and getattr(self.init_module, 'application', None)
- and not environ.get('paste.urlparser.init_application') == environ['SCRIPT_NAME']):
- environ['paste.urlparser.init_application'] = environ['SCRIPT_NAME']
- return self.init_module.application, None
- name, rest_of_path = request.path_info_split(environ['PATH_INFO'])
- environ['PATH_INFO'] = rest_of_path
- if name is not None:
- environ['SCRIPT_NAME'] = environ.get('SCRIPT_NAME', '') + '/' + name
- if not name:
- names = self.index_names
- for index_name in names:
- filename = self.find_file(environ, index_name)
- if filename:
- break
- else:
- # None of the index files found
- filename = None
- else:
- filename = self.find_file(environ, name)
- if filename is None:
- return None, filename
- else:
- return self.get_application(environ, filename), filename
-
- def not_found(self, environ, start_response, debug_message=None):
- exc = httpexceptions.HTTPNotFound(
- 'The resource at %s could not be found'
- % request.construct_url(environ),
- comment='SCRIPT_NAME=%r; PATH_INFO=%r; looking in %r; debug: %s'
- % (environ.get('SCRIPT_NAME'), environ.get('PATH_INFO'),
- self.directory, debug_message or '(none)'))
- return exc.wsgi_application(environ, start_response)
-
- def add_slash(self, environ, start_response):
- """
- This happens when you try to get to a directory
- without a trailing /
- """
- url = request.construct_url(environ, with_query_string=False)
- url += '/'
- if environ.get('QUERY_STRING'):
- url += '?' + environ['QUERY_STRING']
- exc = httpexceptions.HTTPMovedPermanently(
- 'The resource has moved to %s - you should be redirected '
- 'automatically.' % url,
- headers=[('location', url)])
- return exc.wsgi_application(environ, start_response)
-
- def find_file(self, environ, base_filename):
- possible = []
- """Cache a few values to reduce function call overhead"""
- for filename in os.listdir(self.directory):
- base, ext = os.path.splitext(filename)
- full_filename = os.path.join(self.directory, filename)
- if (ext in self.hide_extensions
- or not base):
- continue
- if filename == base_filename:
- possible.append(full_filename)
- continue
- if ext in self.ignore_extensions:
- continue
- if base == base_filename:
- possible.append(full_filename)
- if not possible:
- #environ['wsgi.errors'].write(
- # 'No file found matching %r in %s\n'
- # % (base_filename, self.directory))
- return None
- if len(possible) > 1:
- # If there is an exact match, this isn't 'ambiguous'
- # per se; it might mean foo.gif and foo.gif.back for
- # instance
- if full_filename in possible:
- return full_filename
- else:
- environ['wsgi.errors'].write(
- 'Ambiguous URL: %s; matches files %s\n'
- % (request.construct_url(environ),
- ', '.join(possible)))
- return None
- return possible[0]
-
- def get_application(self, environ, filename):
- if os.path.isdir(filename):
- t = 'dir'
- else:
- t = os.path.splitext(filename)[1]
- constructor = self.constructors.get(t, self.constructors.get('*'))
- if constructor is None:
- #environ['wsgi.errors'].write(
- # 'No constructor found for %s\n' % t)
- return constructor
- app = constructor(self, environ, filename)
- if app is None:
- #environ['wsgi.errors'].write(
- # 'Constructor %s return None for %s\n' %
- # (constructor, filename))
- pass
- return app
-
- def register_constructor(cls, extension, constructor):
- """
- Register a function as a constructor. Registered constructors
- apply to all instances of `URLParser`.
-
- The extension should have a leading ``.``, or the special
- extensions ``dir`` (for directories) and ``*`` (a catch-all).
-
- `constructor` must be a callable that takes two arguments:
- ``environ`` and ``filename``, and returns a WSGI application.
- """
- d = cls.global_constructors
- assert not d.has_key(extension), (
- "A constructor already exists for the extension %r (%r) "
- "when attemption to register constructor %r"
- % (extension, d[extension], constructor))
- d[extension] = constructor
- register_constructor = classmethod(register_constructor)
-
- def get_parser(self, directory, base_python_name):
- """
- Get a parser for the given directory, or create one if
- necessary. This way parsers can be cached and reused.
-
- # @@: settings are inherited from the first caller
- """
- try:
- return self.parsers_by_directory[(directory, base_python_name)]
- except KeyError:
- parser = self.__class__(
- {},
- directory, base_python_name,
- index_names=self.index_names,
- hide_extensions=self.hide_extensions,
- ignore_extensions=self.ignore_extensions,
- constructors=self.constructors)
- self.parsers_by_directory[(directory, base_python_name)] = parser
- return parser
-
- def find_init_module(self, environ):
- filename = os.path.join(self.directory, '__init__.py')
- if not os.path.exists(filename):
- return None
- return load_module(environ, filename)
-
- def __repr__(self):
- return '<%s directory=%r; module=%s at %s>' % (
- self.__class__.__name__,
- self.directory,
- self.base_python_name,
- hex(abs(id(self))))
-
-def make_directory(parser, environ, filename):
- base_python_name = environ['paste.urlparser.base_python_name']
- if base_python_name:
- base_python_name += "." + os.path.basename(filename)
- else:
- base_python_name = os.path.basename(filename)
- return parser.get_parser(filename, base_python_name)
-
-URLParser.register_constructor('dir', make_directory)
-
-def make_unknown(parser, environ, filename):
- return fileapp.FileApp(filename)
-
-URLParser.register_constructor('*', make_unknown)
-
-def load_module(environ, filename):
- base_python_name = environ['paste.urlparser.base_python_name']
- module_name = os.path.splitext(os.path.basename(filename))[0]
- if base_python_name:
- module_name = base_python_name + '.' + module_name
- return load_module_from_name(environ, filename, module_name,
- environ['wsgi.errors'])
-
-def load_module_from_name(environ, filename, module_name, errors):
- if sys.modules.has_key(module_name):
- return sys.modules[module_name]
- init_filename = os.path.join(os.path.dirname(filename), '__init__.py')
- if not os.path.exists(init_filename):
- try:
- f = open(init_filename, 'w')
- except (OSError, IOError), e:
- errors.write(
- 'Cannot write __init__.py file into directory %s (%s)\n'
- % (os.path.dirname(filename), e))
- return None
- f.write('#\n')
- f.close()
- fp = None
- if sys.modules.has_key(module_name):
- return sys.modules[module_name]
- if '.' in module_name:
- parent_name = '.'.join(module_name.split('.')[:-1])
- base_name = module_name.split('.')[-1]
- parent = load_module_from_name(environ, os.path.dirname(filename),
- parent_name, errors)
- else:
- base_name = module_name
- fp = None
- try:
- fp, pathname, stuff = imp.find_module(
- base_name, [os.path.dirname(filename)])
- module = imp.load_module(module_name, fp, pathname, stuff)
- finally:
- if fp is not None:
- fp.close()
- return module
-
-def make_py(parser, environ, filename):
- module = load_module(environ, filename)
- if not module:
- return None
- if hasattr(module, 'application') and module.application:
- return getattr(module.application, 'wsgi_application', module.application)
- base_name = module.__name__.split('.')[-1]
- if hasattr(module, base_name):
- obj = getattr(module, base_name)
- if hasattr(obj, 'wsgi_application'):
- return obj.wsgi_application
- else:
- # @@: Old behavior; should probably be deprecated eventually:
- return getattr(module, base_name)()
- environ['wsgi.errors'].write(
- "Cound not find application or %s in %s\n"
- % (base_name, module))
- return None
-
-URLParser.register_constructor('.py', make_py)
-
-class StaticURLParser(object):
-
- """
- Like ``URLParser`` but only serves static files.
-
- ``cache_max_age``:
- integer specifies Cache-Control max_age in seconds
- """
- # @@: Should URLParser subclass from this?
-
- def __init__(self, directory, root_directory=None,
- cache_max_age=None):
- if os.path.sep != '/':
- directory = directory.replace(os.path.sep, '/')
- self.directory = os.path.normcase(os.path.abspath(directory))
- self.root_directory = root_directory
- if root_directory is not None:
- self.root_directory = os.path.normpath(self.root_directory)
- else:
- self.root_directory = directory
- self.root_directory = os.path.normcase(os.path.normpath(
- os.path.abspath(self.root_directory)))
- self.cache_max_age = cache_max_age
- if os.path.sep != '/':
- directory = directory.replace('/', os.path.sep)
- self.root_directory = self.root_directory.replace('/', os.path.sep)
-
- def __call__(self, environ, start_response):
- path_info = environ.get('PATH_INFO', '')
- if not path_info:
- return self.add_slash(environ, start_response)
- if path_info == '/':
- # @@: This should obviously be configurable
- filename = 'index.html'
- else:
- filename = request.path_info_pop(environ)
- full = os.path.normcase(os.path.normpath(
- os.path.join(self.directory, filename)))
- if os.path.sep != '/':
- full = full.replace('/', os.path.sep)
- if self.root_directory is not None and not full.startswith(self.root_directory):
- # Out of bounds
- return self.not_found(environ, start_response)
- if not os.path.exists(full):
- return self.not_found(environ, start_response)
- if os.path.isdir(full):
- # @@: Cache?
- child_root = self.root_directory is not None and \
- self.root_directory or self.directory
- return self.__class__(full, root_directory=child_root,
- cache_max_age=self.cache_max_age)(environ,
- start_response)
- if environ.get('PATH_INFO') and environ.get('PATH_INFO') != '/':
- return self.error_extra_path(environ, start_response)
- if_none_match = environ.get('HTTP_IF_NONE_MATCH')
- if if_none_match:
- mytime = os.stat(full).st_mtime
- if str(mytime) == if_none_match:
- headers = []
- ETAG.update(headers, mytime)
- start_response('304 Not Modified', headers)
- return [''] # empty body
-
- fa = self.make_app(full)
- if self.cache_max_age:
- fa.cache_control(max_age=self.cache_max_age)
- return fa(environ, start_response)
-
- def make_app(self, filename):
- return fileapp.FileApp(filename)
-
- def add_slash(self, environ, start_response):
- """
- This happens when you try to get to a directory
- without a trailing /
- """
- url = request.construct_url(environ, with_query_string=False)
- url += '/'
- if environ.get('QUERY_STRING'):
- url += '?' + environ['QUERY_STRING']
- exc = httpexceptions.HTTPMovedPermanently(
- 'The resource has moved to %s - you should be redirected '
- 'automatically.' % url,
- headers=[('location', url)])
- return exc.wsgi_application(environ, start_response)
-
- def not_found(self, environ, start_response, debug_message=None):
- exc = httpexceptions.HTTPNotFound(
- 'The resource at %s could not be found'
- % request.construct_url(environ),
- comment='SCRIPT_NAME=%r; PATH_INFO=%r; looking in %r; debug: %s'
- % (environ.get('SCRIPT_NAME'), environ.get('PATH_INFO'),
- self.directory, debug_message or '(none)'))
- return exc.wsgi_application(environ, start_response)
-
- def error_extra_path(self, environ, start_response):
- exc = httpexceptions.HTTPNotFound(
- 'The trailing path %r is not allowed' % environ['PATH_INFO'])
- return exc.wsgi_application(environ, start_response)
-
- def __repr__(self):
- return '<%s %r>' % (self.__class__.__name__, self.directory)
-
-def make_static(global_conf, document_root, cache_max_age=None):
- """
- Return a WSGI application that serves a directory (configured
- with document_root)
-
- cache_max_age - integer specifies CACHE_CONTROL max_age in seconds
- """
- if cache_max_age is not None:
- cache_max_age = int(cache_max_age)
- return StaticURLParser(
- document_root, cache_max_age=cache_max_age)
-
-class PkgResourcesParser(StaticURLParser):
-
- def __init__(self, egg_or_spec, resource_name, manager=None, root_resource=None):
- if pkg_resources is None:
- raise NotImplementedError("This class requires pkg_resources.")
- if isinstance(egg_or_spec, (str, unicode)):
- self.egg = pkg_resources.get_distribution(egg_or_spec)
- else:
- self.egg = egg_or_spec
- self.resource_name = resource_name
- if manager is None:
- manager = pkg_resources.ResourceManager()
- self.manager = manager
- if root_resource is None:
- root_resource = resource_name
- self.root_resource = os.path.normpath(root_resource)
-
- def __repr__(self):
- return '<%s for %s:%r>' % (
- self.__class__.__name__,
- self.egg.project_name,
- self.resource_name)
-
- def __call__(self, environ, start_response):
- path_info = environ.get('PATH_INFO', '')
- if not path_info:
- return self.add_slash(environ, start_response)
- if path_info == '/':
- # @@: This should obviously be configurable
- filename = 'index.html'
- else:
- filename = request.path_info_pop(environ)
- resource = os.path.normcase(os.path.normpath(
- self.resource_name + '/' + filename))
- if self.root_resource is not None and not resource.startswith(self.root_resource):
- # Out of bounds
- return self.not_found(environ, start_response)
- if not self.egg.has_resource(resource):
- return self.not_found(environ, start_response)
- if self.egg.resource_isdir(resource):
- # @@: Cache?
- child_root = self.root_resource is not None and self.root_resource or \
- self.resource_name
- return self.__class__(self.egg, resource, self.manager,
- root_resource=child_root)(environ, start_response)
- if environ.get('PATH_INFO') and environ.get('PATH_INFO') != '/':
- return self.error_extra_path(environ, start_response)
-
- type, encoding = mimetypes.guess_type(resource)
- if not type:
- type = 'application/octet-stream'
- # @@: I don't know what to do with the encoding.
- try:
- file = self.egg.get_resource_stream(self.manager, resource)
- except (IOError, OSError), e:
- exc = httpexceptions.HTTPForbidden(
- 'You are not permitted to view this file (%s)' % e)
- return exc.wsgi_application(environ, start_response)
- start_response('200 OK',
- [('content-type', type)])
- return fileapp._FileIter(file)
-
- def not_found(self, environ, start_response, debug_message=None):
- exc = httpexceptions.HTTPNotFound(
- 'The resource at %s could not be found'
- % request.construct_url(environ),
- comment='SCRIPT_NAME=%r; PATH_INFO=%r; looking in egg:%s#%r; debug: %s'
- % (environ.get('SCRIPT_NAME'), environ.get('PATH_INFO'),
- self.egg, self.resource_name, debug_message or '(none)'))
- return exc.wsgi_application(environ, start_response)
-
-def make_pkg_resources(global_conf, egg, resource_name=''):
- """
- A static file parser that loads data from an egg using
- ``pkg_resources``. Takes a configuration value ``egg``, which is
- an egg spec, and a base ``resource_name`` (default empty string)
- which is the path in the egg that this starts at.
- """
- if pkg_resources is None:
- raise NotImplementedError("This function requires pkg_resources.")
- return PkgResourcesParser(egg, resource_name)
-
-def make_url_parser(global_conf, directory, base_python_name,
- index_names=None, hide_extensions=None,
- ignore_extensions=None,
- **constructor_conf):
- """
- Create a URLParser application that looks in ``directory``, which
- should be the directory for the Python package named in
- ``base_python_name``. ``index_names`` are used when viewing the
- directory (like ``'index'`` for ``'index.html'``).
- ``hide_extensions`` are extensions that are not viewable (like
- ``'.pyc'``) and ``ignore_extensions`` are viewable but only if an
- explicit extension is given.
- """
- if index_names is None:
- index_names = global_conf.get(
- 'index_names', ('index', 'Index', 'main', 'Main'))
- index_names = converters.aslist(index_names)
-
- if hide_extensions is None:
- hide_extensions = global_conf.get(
- 'hide_extensions', ('.pyc', 'bak', 'py~'))
- hide_extensions = converters.aslist(hide_extensions)
-
- if ignore_extensions is None:
- ignore_extensions = global_conf.get(
- 'ignore_extensions', ())
- ignore_extensions = converters.aslist(ignore_extensions)
- # There's no real way to set constructors currently...
-
- return URLParser({}, directory, base_python_name,
- index_names=index_names,
- hide_extensions=hide_extensions,
- ignore_extensions=ignore_extensions,
- **constructor_conf)
-
diff --git a/lib/paste/util/PySourceColor.py b/lib/paste/util/PySourceColor.py
@@ -1,2103 +0,0 @@
-# -*- 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 Jürgen 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
-try :
- import cStringIO as StringIO
-except:
- import 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 Møøse 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 møøse with the sharpened end
- of an interspace tøøthbrush given her by Svenge - her brother-in-law -an Oslo
- dentist and star of many Norwegian møvies: "The Høt Hands of an Oslo
- Dentist", "Fillings of Passion", "The Huge Mølars of Horst Nordfink"..."""
- RU"""142 MEXICAN WHOOPING LLAMAS"""#<-Can you fit 142 llamas in a red box?
- n = u' HERMSGERVØRDENBRØTBØRDA ' + """ 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("&", "&")
- text = text.replace("<", "<")
- text = text.replace(">", ">")
- return text
-
-def unescape(text):
- """unsecape escaped text"""
- text = text.replace(""", '"')
- text = text.replace(">", ">")
- text = text.replace("<", "<")
- text = text.replace("&", "&")
- 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, 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,scol), (erow,ecol), line):
- """Token handler. Order is important do not rearrange."""
- self.line = line
- # 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/lib/paste/util/UserDict24.py b/lib/paste/util/UserDict24.py
@@ -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/lib/paste/util/__init__.py b/lib/paste/util/__init__.py
@@ -1,4 +0,0 @@
-"""
-Package for miscellaneous routines that do not depend on other parts
-of Paste
-"""
diff --git a/lib/paste/util/classinit.py b/lib/paste/util/classinit.py
@@ -1,42 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-class ClassInitMeta(type):
-
- def __new__(meta, class_name, bases, new_attrs):
- cls = type.__new__(meta, class_name, bases, new_attrs)
- if (new_attrs.has_key('__classinit__')
- and not isinstance(cls.__classinit__, staticmethod)):
- setattr(cls, '__classinit__',
- staticmethod(cls.__classinit__.im_func))
- if hasattr(cls, '__classinit__'):
- cls.__classinit__(cls, new_attrs)
- return cls
-
-def build_properties(cls, new_attrs):
- """
- Given a class and a new set of attributes (as passed in by
- __classinit__), create or modify properties based on functions
- with special names ending in __get, __set, and __del.
- """
- for name, value in new_attrs.items():
- if (name.endswith('__get') or name.endswith('__set')
- or name.endswith('__del')):
- base = name[:-5]
- if hasattr(cls, base):
- old_prop = getattr(cls, base)
- if not isinstance(old_prop, property):
- raise ValueError(
- "Attribute %s is a %s, not a property; function %s is named like a property"
- % (base, type(old_prop), name))
- attrs = {'fget': old_prop.fget,
- 'fset': old_prop.fset,
- 'fdel': old_prop.fdel,
- 'doc': old_prop.__doc__}
- else:
- attrs = {}
- attrs['f' + name[-3:]] = value
- if name.endswith('__get') and value.__doc__:
- attrs['doc'] = value.__doc__
- new_prop = property(**attrs)
- setattr(cls, base, new_prop)
diff --git a/lib/paste/util/classinstance.py b/lib/paste/util/classinstance.py
@@ -1,38 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-class classinstancemethod(object):
- """
- Acts like a class method when called from a class, like an
- instance method when called by an instance. The method should
- take two arguments, 'self' and 'cls'; one of these will be None
- depending on how the method was called.
- """
-
- def __init__(self, func):
- self.func = func
- self.__doc__ = func.__doc__
-
- def __get__(self, obj, type=None):
- return _methodwrapper(self.func, obj=obj, type=type)
-
-class _methodwrapper(object):
-
- def __init__(self, func, obj, type):
- self.func = func
- self.obj = obj
- self.type = type
-
- def __call__(self, *args, **kw):
- assert not kw.has_key('self') and not kw.has_key('cls'), (
- "You cannot use 'self' or 'cls' arguments to a "
- "classinstancemethod")
- return self.func(*((self.obj, self.type) + args), **kw)
-
- def __repr__(self):
- if self.obj is None:
- return ('<bound class method %s.%s>'
- % (self.type.__name__, self.func.func_name))
- else:
- return ('<bound method %s.%s of %r>'
- % (self.type.__name__, self.func.func_name, self.obj))
diff --git a/lib/paste/util/converters.py b/lib/paste/util/converters.py
@@ -1,26 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-def asbool(obj):
- if isinstance(obj, (str, unicode)):
- obj = obj.strip().lower()
- if obj in ['true', 'yes', 'on', 'y', 't', '1']:
- return True
- elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
- return False
- else:
- raise ValueError(
- "String is not true/false: %r" % obj)
- return bool(obj)
-
-def aslist(obj, sep=None, strip=True):
- if isinstance(obj, (str, unicode)):
- lst = obj.split(sep)
- if strip:
- lst = [v.strip() for v in lst]
- return lst
- elif isinstance(obj, (list, tuple)):
- return obj
- elif obj is None:
- return []
- else:
- return [obj]
diff --git a/lib/paste/util/dateinterval.py b/lib/paste/util/dateinterval.py
@@ -1,103 +0,0 @@
-"""
-DateInterval.py
-
-Convert interval strings (in the form of 1w2d, etc) to
-seconds, and back again. Is not exactly about months or
-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.
-"""
-
-import re
-
-__all__ = ['interval_decode', 'interval_encode']
-
-second = 1
-minute = second*60
-hour = minute*60
-day = hour*24
-week = day*7
-month = day*30
-year = day*365
-timeValues = {
- 'y': year,
- 'b': month,
- 'w': week,
- 'd': day,
- 'h': hour,
- 'm': minute,
- 's': second,
- }
-timeOrdered = timeValues.items()
-timeOrdered.sort(lambda a, b: -cmp(a[1], b[1]))
-
-def interval_encode(seconds, include_sign=False):
- """Encodes a number of seconds (representing a time interval)
- into a form like 1h2d3s.
-
- >>> interval_encode(10)
- '10s'
- >>> interval_encode(493939)
- '5d17h12m19s'
- """
- s = ''
- orig = seconds
- seconds = abs(seconds)
- for char, amount in timeOrdered:
- if seconds >= amount:
- i, seconds = divmod(seconds, amount)
- s += '%i%s' % (i, char)
- if orig < 0:
- s = '-' + s
- elif not orig:
- return '0'
- elif include_sign:
- s = '+' + s
- return s
-
-_timeRE = re.compile(r'[0-9]+[a-zA-Z]')
-def interval_decode(s):
- """Decodes a number in the format 1h4d3m (1 hour, 3 days, 3 minutes)
- into a number of seconds
-
- >>> interval_decode('40s')
- 40
- >>> interval_decode('10000s')
- 10000
- >>> interval_decode('3d1w45s')
- 864045
- """
- time = 0
- sign = 1
- s = s.strip()
- if s.startswith('-'):
- s = s[1:]
- sign = -1
- elif s.startswith('+'):
- s = s[1:]
- for match in allMatches(s, _timeRE):
- char = match.group(0)[-1].lower()
- if not timeValues.has_key(char):
- # @@: should signal error
- continue
- time += int(match.group(0)[:-1]) * timeValues[char]
- return time
-
-# @@-sgd 2002-12-23 - this function does not belong in this module, find a better place.
-def allMatches(source, regex):
- """Return a list of matches for regex in source
- """
- pos = 0
- end = len(source)
- rv = []
- match = regex.search(source, pos)
- while match:
- rv.append(match)
- match = regex.search(source, match.end() )
- return rv
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/lib/paste/util/datetimeutil.py b/lib/paste/util/datetimeutil.py
@@ -1,361 +0,0 @@
-# (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 = string.lower(val)
- 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 = string.replace(val, noise, ' ')
- val = string.strip(val)
- val = string.split(val)
- 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 = string.lower(val)
- amflag = (-1 != string.find(val, 'a')) # set if AM is found
- pmflag = (-1 != string.find(val, 'p')) # set if PM is found
- for noise in ":amp.":
- val = string.replace(val, noise, ' ')
- val = string.split(val)
- 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 = string.lower(val)
- 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 = string.replace(val, noise, ' ')
- for noise in _wkdy:
- val = string.replace(val, 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 = string.split("".join(out))
- 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)
diff --git a/lib/paste/util/doctest24.py b/lib/paste/util/doctest24.py
@@ -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/lib/paste/util/filemixin.py b/lib/paste/util/filemixin.py
@@ -1,53 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-class FileMixin(object):
-
- """
- Used to provide auxiliary methods to objects simulating files.
- Objects must implement write, and read if they are input files.
- Also they should implement close.
-
- Other methods you may wish to override:
- * flush()
- * seek(offset[, whence])
- * tell()
- * truncate([size])
-
- Attributes you may wish to provide:
- * closed
- * encoding (you should also respect that in write())
- * mode
- * newlines (hard to support)
- * softspace
- """
-
- def flush(self):
- pass
-
- def next(self):
- return self.readline()
-
- def readline(self, size=None):
- # @@: This is a lame implementation; but a buffer would probably
- # be necessary for a better implementation
- output = []
- while 1:
- next = self.read(1)
- if not next:
- return ''.join(output)
- output.append(next)
- if size and size > 0 and len(output) >= size:
- return ''.join(output)
- if next == '\n':
- # @@: also \r?
- return ''.join(output)
-
- def xreadlines(self):
- return self
-
- def writelines(self, lines):
- for line in lines:
- self.write(line)
-
-
diff --git a/lib/paste/util/finddata.py b/lib/paste/util/finddata.py
@@ -1,99 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-# Note: you may want to copy this into your setup.py file verbatim, as
-# you can't import this from another package, when you don't know if
-# that package is installed yet.
-
-import os
-import sys
-from fnmatch import fnmatchcase
-from distutils.util import convert_path
-
-# Provided as an attribute, so you can append to these instead
-# of replicating them:
-standard_exclude = ('*.py', '*.pyc', '*$py.class', '*~', '.*', '*.bak')
-standard_exclude_directories = ('.*', 'CVS', '_darcs', './build',
- './dist', 'EGG-INFO', '*.egg-info')
-
-def find_package_data(
- where='.', package='',
- exclude=standard_exclude,
- exclude_directories=standard_exclude_directories,
- only_in_packages=True,
- show_ignored=False):
- """
- Return a dictionary suitable for use in ``package_data``
- in a distutils ``setup.py`` file.
-
- The dictionary looks like::
-
- {'package': [files]}
-
- Where ``files`` is a list of all the files in that package that
- don't match anything in ``exclude``.
-
- If ``only_in_packages`` is true, then top-level directories that
- are not packages won't be included (but directories under packages
- will).
-
- Directories matching any pattern in ``exclude_directories`` will
- be ignored; by default directories with leading ``.``, ``CVS``,
- and ``_darcs`` will be ignored.
-
- If ``show_ignored`` is true, then all the files that aren't
- included in package data are shown on stderr (for debugging
- purposes).
-
- 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:
- where, prefix, package, only_in_packages = stack.pop(0)
- for name in os.listdir(where):
- fn = os.path.join(where, name)
- if os.path.isdir(fn):
- bad_name = False
- for pattern in exclude_directories:
- if (fnmatchcase(name, pattern)
- or fn.lower() == pattern.lower()):
- bad_name = True
- if show_ignored:
- print >> sys.stderr, (
- "Directory %s ignored by pattern %s"
- % (fn, pattern))
- break
- if bad_name:
- continue
- if (os.path.isfile(os.path.join(fn, '__init__.py'))
- and not prefix):
- if not package:
- new_package = name
- else:
- new_package = package + '.' + name
- stack.append((fn, '', new_package, False))
- else:
- stack.append((fn, prefix + name + '/', package, only_in_packages))
- elif package or not only_in_packages:
- # is a file
- bad_name = False
- for pattern in exclude:
- if (fnmatchcase(name, pattern)
- or fn.lower() == pattern.lower()):
- bad_name = True
- if show_ignored:
- print >> sys.stderr, (
- "File %s ignored by pattern %s"
- % (fn, pattern))
- break
- if bad_name:
- continue
- out.setdefault(package, []).append(prefix+name)
- return out
-
-if __name__ == '__main__':
- import pprint
- pprint.pprint(
- find_package_data(show_ignored=True))
diff --git a/lib/paste/util/findpackage.py b/lib/paste/util/findpackage.py
@@ -1,26 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-import sys
-import os
-
-def find_package(dir):
- """
- Given a directory, finds the equivalent package name. If it
- is directly in sys.path, returns ''.
- """
- dir = os.path.abspath(dir)
- orig_dir = dir
- path = map(os.path.abspath, sys.path)
- packages = []
- last_dir = None
- while 1:
- if dir in path:
- return '.'.join(packages)
- packages.insert(0, os.path.basename(dir))
- dir = os.path.dirname(dir)
- if last_dir == dir:
- raise ValueError(
- "%s is not under any path found in sys.path" % orig_dir)
- last_dir = dir
-
diff --git a/lib/paste/util/import_string.py b/lib/paste/util/import_string.py
@@ -1,95 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-'imports' a string -- converts a string to a Python object, importing
-any necessary modules and evaluating the expression. Everything
-before the : in an import expression is the module path; everything
-after is an expression to be evaluated in the namespace of that
-module.
-
-Alternately, if no : is present, then import the modules and get the
-attributes as necessary. Arbitrary expressions are not allowed in
-that case.
-"""
-
-def eval_import(s):
- """
- Import a module, or import an object from a module.
-
- A module name like ``foo.bar:baz()`` can be used, where
- ``foo.bar`` is the module, and ``baz()`` is an expression
- evaluated in the context of that module. Note this is not safe on
- arbitrary strings because of the eval.
- """
- if ':' not in s:
- return simple_import(s)
- module_name, expr = s.split(':', 1)
- module = import_module(module_name)
- obj = eval(expr, module.__dict__)
- return obj
-
-def simple_import(s):
- """
- Import a module, or import an object from a module.
-
- A name like ``foo.bar.baz`` can be a module ``foo.bar.baz`` or a
- module ``foo.bar`` with an object ``baz`` in it, or a module
- ``foo`` with an object ``bar`` with an attribute ``baz``.
- """
- parts = s.split('.')
- module = import_module(parts[0])
- name = parts[0]
- parts = parts[1:]
- last_import_error = None
- while parts:
- name += '.' + parts[0]
- try:
- module = import_module(name)
- parts = parts[1:]
- except ImportError, e:
- last_import_error = e
- break
- obj = module
- while parts:
- try:
- obj = getattr(module, parts[0])
- except AttributeError:
- raise ImportError(
- "Cannot find %s in module %r (stopped importing modules with error %s)" % (parts[0], module, last_import_error))
- parts = parts[1:]
- return obj
-
-def import_module(s):
- """
- Import a module.
- """
- mod = __import__(s)
- parts = s.split('.')
- for part in parts[1:]:
- mod = getattr(mod, part)
- return mod
-
-def try_import_module(module_name):
- """
- Imports a module, but catches import errors. Only catches errors
- when that module doesn't exist; if that module itself has an
- import error it will still get raised. Returns None if the module
- doesn't exist.
- """
- try:
- return import_module(module_name)
- except ImportError, e:
- if not getattr(e, 'args', None):
- raise
- desc = e.args[0]
- if not desc.startswith('No module named '):
- raise
- desc = desc[len('No module named '):]
- # If you import foo.bar.baz, the bad import could be any
- # of foo.bar.baz, bar.baz, or baz; we'll test them all:
- parts = module_name.split('.')
- for i in range(len(parts)):
- if desc == '.'.join(parts[i:]):
- return None
- raise
diff --git a/lib/paste/util/intset.py b/lib/paste/util/intset.py
@@ -1,511 +0,0 @@
-# -*- coding: iso-8859-15 -*-
-"""Immutable integer set type.
-
-Integer set class.
-
-Copyright (C) 2006, Heiko Wundram.
-Released under the MIT license.
-"""
-
-# Version information
-# -------------------
-
-__author__ = "Heiko Wundram <me@modelnine.org>"
-__version__ = "0.2"
-__revision__ = "6"
-__date__ = "2006-01-20"
-
-
-# Utility classes
-# ---------------
-
-class _Infinity(object):
- """Internal type used to represent infinity values."""
-
- __slots__ = ["_neg"]
-
- def __init__(self,neg):
- self._neg = neg
-
- def __lt__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
- return NotImplemented
- return ( self._neg and
- not ( isinstance(value,_Infinity) and value._neg ) )
-
- def __le__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
- return NotImplemented
- return self._neg
-
- def __gt__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
- 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)):
- return NotImplemented
- return not self._neg
-
- def __eq__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
- return NotImplemented
- return isinstance(value,_Infinity) and self._neg == value._neg
-
- def __ne__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
- return NotImplemented
- return not isinstance(value,_Infinity) or self._neg <> value._neg
-
- def __repr__(self):
- return "None"
-
-
-# Constants
-# ---------
-
-_MININF = _Infinity(True)
-_MAXINF = _Infinity(False)
-
-
-# Integer set class
-# -----------------
-
-class IntSet(object):
- """Integer set class with efficient storage in a RLE format of ranges.
- Supports minus and plus infinity in the range."""
-
- __slots__ = ["_ranges","_min","_max","_hash"]
-
- def __init__(self,*args,**kwargs):
- """Initialize an integer set. The constructor accepts an unlimited
- number of arguments that may either be tuples in the form of
- (start,stop) where either start or stop may be a number or None to
- represent maximum/minimum in that direction. The range specified by
- (start,stop) is always inclusive (differing from the builtin range
- operator).
-
- Keyword arguments that can be passed to an integer set are min and
- max, which specify the minimum and maximum number in the set,
- respectively. You can also pass None here to represent minus or plus
- infinity, which is also the default.
- """
-
- # Special case copy constructor.
- if len(args) == 1 and isinstance(args[0],IntSet):
- if kwargs:
- raise ValueError("No keyword arguments for copy constructor.")
- self._min = args[0]._min
- self._max = args[0]._max
- self._ranges = args[0]._ranges
- self._hash = args[0]._hash
- return
-
- # Initialize set.
- self._ranges = []
-
- # Process keyword arguments.
- self._min = kwargs.pop("min",_MININF)
- self._max = kwargs.pop("max",_MAXINF)
- if self._min is None:
- self._min = _MININF
- if self._max is None:
- self._max = _MAXINF
-
- # Check keyword arguments.
- if kwargs:
- raise ValueError("Invalid keyword argument.")
- if not ( isinstance(self._min,(int,long)) or self._min is _MININF ):
- raise TypeError("Invalid type of min argument.")
- if not ( isinstance(self._max,(int,long)) 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)):
- self._max += 1
-
- # Process arguments.
- for arg in args:
- if isinstance(arg,(int,long)):
- start, stop = arg, arg+1
- elif isinstance(arg,tuple):
- if len(arg) <> 2:
- raise ValueError("Invalid tuple, must be (start,stop).")
-
- # Process argument.
- start, stop = arg
- if start is None:
- start = self._min
- if stop is None:
- stop = self._max
-
- # Check arguments.
- if not ( isinstance(start,(int,long)) or start is _MININF ):
- raise TypeError("Invalid type of tuple start.")
- if not ( isinstance(stop,(int,long)) 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)):
- stop += 1
- else:
- raise TypeError("Invalid argument.")
-
- if start > self._max:
- continue
- elif start < self._min:
- start = self._min
- if stop < self._min:
- continue
- elif stop > self._max:
- stop = self._max
- self._ranges.append((start,stop))
-
- # Normalize set.
- self._normalize()
-
- # Utility functions for set operations
- # ------------------------------------
-
- def _iterranges(self,r1,r2,minval=_MININF,maxval=_MAXINF):
- curval = minval
- curstates = {"r1":False,"r2":False}
- imax, jmax = 2*len(r1), 2*len(r2)
- i, j = 0, 0
- while i < imax or j < jmax:
- if i < imax and ( ( j < jmax and
- r1[i>>1][i&1] < r2[j>>1][j&1] ) or
- j == jmax ):
- cur_r, newname, newstate = r1[i>>1][i&1], "r1", not (i&1)
- i += 1
- else:
- cur_r, newname, newstate = r2[j>>1][j&1], "r2", not (j&1)
- j += 1
- if curval < cur_r:
- if cur_r > maxval:
- break
- yield curstates, (curval,cur_r)
- curval = cur_r
- curstates[newname] = newstate
- if curval < maxval:
- yield curstates, (curval,maxval)
-
- def _normalize(self):
- self._ranges.sort()
- i = 1
- while i < len(self._ranges):
- if self._ranges[i][0] < self._ranges[i-1][1]:
- self._ranges[i-1] = (self._ranges[i-1][0],
- max(self._ranges[i-1][1],
- self._ranges[i][1]))
- del self._ranges[i]
- else:
- i += 1
- self._ranges = tuple(self._ranges)
- self._hash = hash(self._ranges)
-
- def __coerce__(self,other):
- if isinstance(other,IntSet):
- return self, other
- elif isinstance(other,(int,long,tuple)):
- try:
- return self, self.__class__(other)
- except TypeError:
- # Catch a type error, in that case the structure specified by
- # other is something we can't coerce, return NotImplemented.
- # ValueErrors are not caught, they signal that the data was
- # invalid for the constructor. This is appropriate to signal
- # as a ValueError to the caller.
- return NotImplemented
- elif isinstance(other,list):
- try:
- return self, self.__class__(*other)
- except TypeError:
- # See above.
- return NotImplemented
- return NotImplemented
-
- # Set function definitions
- # ------------------------
-
- def _make_function(name,type,doc,pall,pany=None):
- """Makes a function to match two ranges. Accepts two types: either
- 'set', which defines a function which returns a set with all ranges
- matching pall (pany is ignored), or 'bool', which returns True if pall
- matches for all ranges and pany matches for any one range. doc is the
- dostring to give this function. pany may be none to ignore the any
- match.
-
- The predicates get a dict with two keys, 'r1', 'r2', which denote
- whether the current range is present in range1 (self) and/or range2
- (other) or none of the two, respectively."""
-
- if type == "set":
- def f(self,other):
- coerced = self.__coerce__(other)
- if coerced is NotImplemented:
- return NotImplemented
- other = coerced[1]
- newset = self.__class__.__new__(self.__class__)
- newset._min = min(self._min,other._min)
- newset._max = max(self._max,other._max)
- newset._ranges = []
- for states, (start,stop) in \
- self._iterranges(self._ranges,other._ranges,
- newset._min,newset._max):
- if pall(states):
- if newset._ranges and newset._ranges[-1][1] == start:
- newset._ranges[-1] = (newset._ranges[-1][0],stop)
- else:
- newset._ranges.append((start,stop))
- newset._ranges = tuple(newset._ranges)
- newset._hash = hash(self._ranges)
- return newset
- elif type == "bool":
- def f(self,other):
- coerced = self.__coerce__(other)
- if coerced is NotImplemented:
- return NotImplemented
- other = coerced[1]
- _min = min(self._min,other._min)
- _max = max(self._max,other._max)
- found = not pany
- for states, (start,stop) in \
- self._iterranges(self._ranges,other._ranges,_min,_max):
- if not pall(states):
- return False
- found = found or pany(states)
- return found
- else:
- raise ValueError("Invalid type of function to create.")
- try:
- f.func_name = name
- except TypeError:
- pass
- f.func_doc = doc
- return f
-
- # Intersection.
- __and__ = _make_function("__and__","set",
- "Intersection of two sets as a new set.",
- lambda s: s["r1"] and s["r2"])
- __rand__ = _make_function("__rand__","set",
- "Intersection of two sets as a new set.",
- lambda s: s["r1"] and s["r2"])
- intersection = _make_function("intersection","set",
- "Intersection of two sets as a new set.",
- lambda s: s["r1"] and s["r2"])
-
- # Union.
- __or__ = _make_function("__or__","set",
- "Union of two sets as a new set.",
- lambda s: s["r1"] or s["r2"])
- __ror__ = _make_function("__ror__","set",
- "Union of two sets as a new set.",
- lambda s: s["r1"] or s["r2"])
- union = _make_function("union","set",
- "Union of two sets as a new set.",
- lambda s: s["r1"] or s["r2"])
-
- # Difference.
- __sub__ = _make_function("__sub__","set",
- "Difference of two sets as a new set.",
- lambda s: s["r1"] and not s["r2"])
- __rsub__ = _make_function("__rsub__","set",
- "Difference of two sets as a new set.",
- lambda s: s["r2"] and not s["r1"])
- difference = _make_function("difference","set",
- "Difference of two sets as a new set.",
- lambda s: s["r1"] and not s["r2"])
-
- # Symmetric difference.
- __xor__ = _make_function("__xor__","set",
- "Symmetric difference of two sets as a new set.",
- lambda s: s["r1"] ^ s["r2"])
- __rxor__ = _make_function("__rxor__","set",
- "Symmetric difference of two sets as a new set.",
- lambda s: s["r1"] ^ s["r2"])
- symmetric_difference = _make_function("symmetric_difference","set",
- "Symmetric difference of two sets as a new set.",
- lambda s: s["r1"] ^ s["r2"])
-
- # Containership testing.
- __contains__ = _make_function("__contains__","bool",
- "Returns true if self is superset of other.",
- lambda s: s["r1"] or not s["r2"])
- issubset = _make_function("issubset","bool",
- "Returns true if self is subset of other.",
- lambda s: s["r2"] or not s["r1"])
- istruesubset = _make_function("istruesubset","bool",
- "Returns true if self is true subset of other.",
- lambda s: s["r2"] or not s["r1"],
- lambda s: s["r2"] and not s["r1"])
- issuperset = _make_function("issuperset","bool",
- "Returns true if self is superset of other.",
- lambda s: s["r1"] or not s["r2"])
- istruesuperset = _make_function("istruesuperset","bool",
- "Returns true if self is true superset of other.",
- lambda s: s["r1"] or not s["r2"],
- lambda s: s["r1"] and not s["r2"])
- overlaps = _make_function("overlaps","bool",
- "Returns true if self overlaps with other.",
- lambda s: True,
- lambda s: s["r1"] and s["r2"])
-
- # Comparison.
- __eq__ = _make_function("__eq__","bool",
- "Returns true if self is equal to other.",
- lambda s: not ( s["r1"] ^ s["r2"] ))
- __ne__ = _make_function("__ne__","bool",
- "Returns true if self is different to other.",
- lambda s: True,
- lambda s: s["r1"] ^ s["r2"])
-
- # Clean up namespace.
- del _make_function
-
- # Define other functions.
- def inverse(self):
- """Inverse of set as a new set."""
-
- newset = self.__class__.__new__(self.__class__)
- newset._min = self._min
- newset._max = self._max
- newset._ranges = []
- laststop = self._min
- for r in self._ranges:
- if laststop < r[0]:
- newset._ranges.append((laststop,r[0]))
- laststop = r[1]
- if laststop < self._max:
- newset._ranges.append((laststop,self._max))
- return newset
-
- __invert__ = inverse
-
- # Hashing
- # -------
-
- def __hash__(self):
- """Returns a hash value representing this integer set. As the set is
- always stored normalized, the hash value is guaranteed to match for
- matching ranges."""
-
- return self._hash
-
- # Iterating
- # ---------
-
- def __len__(self):
- """Get length of this integer set. In case the length is larger than
- 2**31 (including infinitely sized integer sets), it raises an
- OverflowError. This is due to len() restricting the size to
- 0 <= len < 2**31."""
-
- if not self._ranges:
- return 0
- if self._ranges[0][0] is _MININF or self._ranges[-1][1] is _MAXINF:
- raise OverflowError("Infinitely sized integer set.")
- rlen = 0
- for r in self._ranges:
- rlen += r[1]-r[0]
- if rlen >= 2**31:
- raise OverflowError("Integer set bigger than 2**31.")
- return rlen
-
- def len(self):
- """Returns the length of this integer set as an integer. In case the
- length is infinite, returns -1. This function exists because of a
- limitation of the builtin len() function which expects values in
- the range 0 <= len < 2**31. Use this function in case your integer
- set might be larger."""
-
- if not self._ranges:
- return 0
- if self._ranges[0][0] is _MININF or self._ranges[-1][1] is _MAXINF:
- return -1
- rlen = 0
- for r in self._ranges:
- rlen += r[1]-r[0]
- return rlen
-
- def __nonzero__(self):
- """Returns true if this integer set contains at least one item."""
-
- return bool(self._ranges)
-
- def __iter__(self):
- """Iterate over all values in this integer set. Iteration always starts
- by iterating from lowest to highest over the ranges that are bounded.
- After processing these, all ranges that are unbounded (maximum 2) are
- yielded intermixed."""
-
- ubranges = []
- for r in self._ranges:
- if r[0] is _MININF:
- if r[1] is _MAXINF:
- ubranges.extend(([0,1],[-1,-1]))
- else:
- ubranges.append([r[1]-1,-1])
- elif r[1] is _MAXINF:
- ubranges.append([r[0],1])
- else:
- for val in xrange(r[0],r[1]):
- yield val
- if ubranges:
- while True:
- for ubrange in ubranges:
- yield ubrange[0]
- ubrange[0] += ubrange[1]
-
- # Printing
- # --------
-
- def __repr__(self):
- """Return a representation of this integer set. The representation is
- executable to get an equal integer set."""
-
- rv = []
- for start, stop in self._ranges:
- if ( isinstance(start,(int,long)) and isinstance(stop,(int,long))
- and stop-start == 1 ):
- rv.append("%r" % start)
- elif isinstance(stop,(int,long)):
- rv.append("(%r,%r)" % (start,stop-1))
- else:
- rv.append("(%r,%r)" % (start,stop))
- if self._min is not _MININF:
- rv.append("min=%r" % self._min)
- if self._max is not _MAXINF:
- rv.append("max=%r" % self._max)
- return "%s(%s)" % (self.__class__.__name__,",".join(rv))
-
-if __name__ == "__main__":
- # Little test script demonstrating functionality.
- x = IntSet((10,20),30)
- y = IntSet((10,20))
- z = IntSet((10,20),30,(15,19),min=0,max=40)
- print x
- print x&110
- print x|110
- print x^(15,25)
- print x-12
- print 12 in x
- print x.issubset(x)
- print y.issubset(x)
- print x.istruesubset(x)
- print y.istruesubset(x)
- for val in x:
- print val
- print x.inverse()
- print x == z
- print x == y
- print x <> y
- print hash(x)
- print hash(z)
- print len(x)
- print x.len()
diff --git a/lib/paste/util/ip4.py b/lib/paste/util/ip4.py
@@ -1,273 +0,0 @@
-# -*- coding: iso-8859-15 -*-
-"""IP4 address range set implementation.
-
-Implements an IPv4-range type.
-
-Copyright (C) 2006, Heiko Wundram.
-Released under the MIT-license.
-"""
-
-# Version information
-# -------------------
-
-__author__ = "Heiko Wundram <me@modelnine.org>"
-__version__ = "0.2"
-__revision__ = "3"
-__date__ = "2006-01-20"
-
-
-# Imports
-# -------
-
-import intset
-import socket
-
-
-# IP4Range class
-# --------------
-
-class IP4Range(intset.IntSet):
- """IP4 address range class with efficient storage of address ranges.
- Supports all set operations."""
-
- _MINIP4 = 0
- _MAXIP4 = (1<<32) - 1
- _UNITYTRANS = "".join([chr(n) for n in range(256)])
- _IPREMOVE = "0123456789."
-
- def __init__(self,*args):
- """Initialize an ip4range class. The constructor accepts an unlimited
- number of arguments that may either be tuples in the form (start,stop),
- integers, longs or strings, where start and stop in a tuple may
- also be of the form integer, long or string.
-
- Passing an integer or long means passing an IPv4-address that's already
- been converted to integer notation, whereas passing a string specifies
- an address where this conversion still has to be done. A string
- address may be in the following formats:
-
- - 1.2.3.4 - a plain address, interpreted as a single address
- - 1.2.3 - a set of addresses, interpreted as 1.2.3.0-1.2.3.255
- - localhost - hostname to look up, interpreted as single address
- - 1.2.3<->5 - a set of addresses, interpreted as 1.2.3.0-1.2.5.255
- - 1.2.0.0/16 - a set of addresses, interpreted as 1.2.0.0-1.2.255.255
-
- Only the first three notations are valid if you use a string address in
- a tuple, whereby notation 2 is interpreted as 1.2.3.0 if specified as
- lower bound and 1.2.3.255 if specified as upper bound, not as a range
- of addresses.
-
- Specifying a range is done with the <-> operator. This is necessary
- because '-' might be present in a hostname. '<->' shouldn't be, ever.
- """
-
- # Special case copy constructor.
- if len(args) == 1 and isinstance(args[0],IP4Range):
- super(IP4Range,self).__init__(args[0])
- return
-
- # Convert arguments to tuple syntax.
- args = list(args)
- for i in range(len(args)):
- argval = args[i]
- if isinstance(argval,str):
- if "<->" in argval:
- # Type 4 address.
- args[i] = self._parseRange(*argval.split("<->",1))
- continue
- elif "/" in argval:
- # Type 5 address.
- args[i] = self._parseMask(*argval.split("/",1))
- else:
- # Type 1, 2 or 3.
- args[i] = self._parseAddrRange(argval)
- elif isinstance(argval,tuple):
- if len(tuple) <> 2:
- raise ValueError("Tuple is of invalid length.")
- addr1, addr2 = argval
- if isinstance(addr1,str):
- addr1 = self._parseAddrRange(addr1)[0]
- elif not isinstance(addr1,(int,long)):
- raise TypeError("Invalid argument.")
- if isinstance(addr2,str):
- addr2 = self._parseAddrRange(addr2)[1]
- elif not isinstance(addr2,(int,long)):
- raise TypeError("Invalid argument.")
- args[i] = (addr1,addr2)
- elif not isinstance(argval,(int,long)):
- raise TypeError("Invalid argument.")
-
- # Initialize the integer set.
- super(IP4Range,self).__init__(min=self._MINIP4,max=self._MAXIP4,*args)
-
- # Parsing functions
- # -----------------
-
- def _parseRange(self,addr1,addr2):
- naddr1, naddr1len = _parseAddr(addr1)
- naddr2, naddr2len = _parseAddr(addr2)
- if naddr2len < naddr1len:
- naddr2 += naddr1&(((1<<((naddr1len-naddr2len)*8))-1)<<
- (naddr2len*8))
- naddr2len = naddr1len
- elif naddr2len > naddr1len:
- raise ValueError("Range has more dots than address.")
- naddr1 <<= (4-naddr1len)*8
- naddr2 <<= (4-naddr2len)*8
- naddr2 += (1<<((4-naddr2len)*8))-1
- return (naddr1,naddr2)
-
- def _parseMask(self,addr,mask):
- naddr, naddrlen = _parseAddr(addr)
- naddr <<= (4-naddrlen)*8
- try:
- if not mask:
- masklen = 0
- else:
- masklen = int(mask)
- if not 0 <= masklen <= 32:
- raise ValueError
- except ValueError:
- try:
- mask = _parseAddr(mask,False)
- except ValueError:
- raise ValueError("Mask isn't parseable.")
- remaining = 0
- masklen = 0
- if not mask:
- masklen = 0
- else:
- while not (mask&1):
- remaining += 1
- while (mask&1):
- mask >>= 1
- masklen += 1
- if remaining+masklen <> 32:
- raise ValueError("Mask isn't a proper host mask.")
- naddr1 = naddr & (((1<<masklen)-1)<<(32-masklen))
- naddr2 = naddr1 + (1<<(32-masklen)) - 1
- return (naddr1,naddr2)
-
- def _parseAddrRange(self,addr):
- naddr, naddrlen = _parseAddr(addr)
- naddr1 = naddr<<((4-naddrlen)*8)
- naddr2 = ( (naddr<<((4-naddrlen)*8)) +
- (1<<((4-naddrlen)*8)) - 1 )
- return (naddr1,naddr2)
-
- # Utility functions
- # -----------------
-
- def _int2ip(self,num):
- rv = []
- for i in range(4):
- rv.append(str(num&255))
- num >>= 8
- return ".".join(reversed(rv))
-
- # Iterating
- # ---------
-
- def iteraddresses(self):
- """Returns an iterator which iterates over ips in this iprange. An
- IP is returned in string form (e.g. '1.2.3.4')."""
-
- for v in super(IP4Range,self).__iter__():
- yield self._int2ip(v)
-
- def iterranges(self):
- """Returns an iterator which iterates over ip-ip ranges which build
- this iprange if combined. An ip-ip pair is returned in string form
- (e.g. '1.2.3.4-2.3.4.5')."""
-
- for r in self._ranges:
- if r[1]-r[0] == 1:
- yield self._int2ip(r[0])
- else:
- yield '%s-%s' % (self._int2ip(r[0]),self._int2ip(r[1]-1))
-
- def itermasks(self):
- """Returns an iterator which iterates over ip/mask pairs which build
- this iprange if combined. An IP/Mask pair is returned in string form
- (e.g. '1.2.3.0/24')."""
-
- for r in self._ranges:
- for v in self._itermasks(r):
- yield v
-
- def _itermasks(self,r):
- ranges = [r]
- while ranges:
- cur = ranges.pop()
- curmask = 0
- while True:
- curmasklen = 1<<(32-curmask)
- start = (cur[0]+curmasklen-1)&(((1<<curmask)-1)<<(32-curmask))
- if start >= cur[0] and start+curmasklen <= cur[1]:
- break
- else:
- curmask += 1
- yield "%s/%s" % (self._int2ip(start),curmask)
- if cur[0] < start:
- ranges.append((cur[0],start))
- if cur[1] > start+curmasklen:
- ranges.append((start+curmasklen,cur[1]))
-
- __iter__ = iteraddresses
-
- # Printing
- # --------
-
- def __repr__(self):
- """Returns a string which can be used to reconstruct this iprange."""
-
- rv = []
- for start, stop in self._ranges:
- if stop-start == 1:
- rv.append("%r" % (self._int2ip(start),))
- else:
- rv.append("(%r,%r)" % (self._int2ip(start),
- self._int2ip(stop-1)))
- return "%s(%s)" % (self.__class__.__name__,",".join(rv))
-
-def _parseAddr(addr,lookup=True):
- if lookup and addr.translate(IP4Range._UNITYTRANS, IP4Range._IPREMOVE):
- try:
- addr = socket.gethostbyname(addr)
- except socket.error:
- raise ValueError("Invalid Hostname as argument.")
- naddr = 0
- for naddrpos, part in enumerate(addr.split(".")):
- if naddrpos >= 4:
- raise ValueError("Address contains more than four parts.")
- try:
- if not part:
- part = 0
- else:
- part = int(part)
- if not 0 <= part < 256:
- raise ValueError
- except ValueError:
- raise ValueError("Address part out of range.")
- naddr <<= 8
- naddr += part
- return naddr, naddrpos+1
-
-def ip2int(addr, lookup=True):
- return _parseAddr(addr, lookup=lookup)[0]
-
-if __name__ == "__main__":
- # Little test script.
- x = IP4Range("172.22.162.250/24")
- y = IP4Range("172.22.162.250","172.22.163.250","172.22.163.253<->255")
- print x
- for val in x.itermasks():
- print val
- for val in y.itermasks():
- print val
- for val in (x|y).itermasks():
- print val
- for val in (x^y).iterranges():
- print val
- for val in x:
- print val
diff --git a/lib/paste/util/killthread.py b/lib/paste/util/killthread.py
@@ -1,30 +0,0 @@
-"""
-Kill a thread, from http://sebulba.wikispaces.com/recipe+thread2
-"""
-import types
-try:
- import ctypes
-except ImportError:
- raise ImportError(
- "You cannot use paste.util.killthread without ctypes installed")
-if not hasattr(ctypes, 'pythonapi'):
- raise ImportError(
- "You cannot use paste.util.killthread without ctypes.pythonapi")
-
-def async_raise(tid, exctype):
- """raises the exception, performs cleanup if needed.
-
- tid is the value given by thread.get_ident() (an integer).
- Raise SystemExit to kill a thread."""
- if not isinstance(exctype, (types.ClassType, type)):
- raise TypeError("Only types can be raised (not instances)")
- if not isinstance(tid, int):
- raise TypeError("tid must be an integer")
- res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
- if res == 0:
- raise ValueError("invalid thread id")
- elif res != 1:
- # """if it returns a number greater than one, you're in trouble,
- # and you should call it again with exc=NULL to revert the effect"""
- ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
- raise SystemError("PyThreadState_SetAsyncExc failed")
diff --git a/lib/paste/util/looper.py b/lib/paste/util/looper.py
@@ -1,152 +0,0 @@
-"""
-Helper for looping over sequences, particular in templates.
-
-Often in a loop in a template it's handy to know what's next up,
-previously up, if this is the first or last item in the sequence, etc.
-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
- ... if not loop.last:
- ... print '---'
- 1 a
- ---
- 2 b
- ---
- 3 c
-
-"""
-
-__all__ = ['looper']
-
-class looper(object):
- """
- Helper for looping (particularly in templates)
-
- Use this like::
-
- for loop, item in looper(seq):
- if loop.first:
- ...
- """
-
- def __init__(self, seq):
- self.seq = seq
-
- def __iter__(self):
- return looper_iter(self.seq)
-
- def __repr__(self):
- return '<%s for %r>' % (
- self.__class__.__name__, self.seq)
-
-class looper_iter(object):
-
- def __init__(self, seq):
- self.seq = list(seq)
- self.pos = 0
-
- def __iter__(self):
- return self
-
- def next(self):
- if self.pos >= len(self.seq):
- raise StopIteration
- result = loop_pos(self.seq, self.pos), self.seq[self.pos]
- self.pos += 1
- return result
-
-class loop_pos(object):
-
- def __init__(self, seq, pos):
- self.seq = seq
- self.pos = pos
-
- def __repr__(self):
- return '<loop pos=%r at %r>' % (
- self.seq[pos], pos)
-
- def index(self):
- return self.pos
- index = property(index)
-
- def number(self):
- return self.pos + 1
- number = property(number)
-
- def item(self):
- return self.seq[self.pos]
- item = property(item)
-
- def next(self):
- try:
- return self.seq[self.pos+1]
- except IndexError:
- return None
- next = property(next)
-
- def previous(self):
- if self.pos == 0:
- return None
- return self.seq[self.pos-1]
- previous = property(previous)
-
- def odd(self):
- return not self.pos % 2
- odd = property(odd)
-
- def even(self):
- return self.pos % 2
- even = property(even)
-
- def first(self):
- return self.pos == 0
- first = property(first)
-
- def last(self):
- return self.pos == len(self.seq)-1
- last = property(last)
-
- def length(self):
- return len(self.seq)
- length = property(length)
-
- def first_group(self, getter=None):
- """
- Returns true if this item is the start of a new group,
- where groups mean that some attribute has changed. The getter
- can be None (the item itself changes), an attribute name like
- ``'.attr'``, a function, or a dict key or list index.
- """
- if self.first:
- return True
- return self._compare_group(self.item, self.previous, getter)
-
- def last_group(self, getter=None):
- """
- Returns true if this item is the end of a new group,
- where groups mean that some attribute has changed. The getter
- can be None (the item itself changes), an attribute name like
- ``'.attr'``, a function, or a dict key or list index.
- """
- if self.last:
- return True
- return self._compare_group(self.item, self.next, getter)
-
- def _compare_group(self, item, other, getter):
- if getter is None:
- return item != other
- elif (isinstance(getter, basestring)
- and getter.startswith('.')):
- getter = getter[1:]
- if getter.endswith('()'):
- getter = getter[:-2]
- return getattr(item, getter)() != getattr(other, getter)()
- else:
- return getattr(item, getter) != getattr(other, getter)
- elif callable(getter):
- return getter(item) != getter(other)
- else:
- return item[getter] != other[getter]
-
diff --git a/lib/paste/util/mimeparse.py b/lib/paste/util/mimeparse.py
@@ -1,178 +0,0 @@
-"""MIME-Type Parser
-
-This module provides basic functions for handling mime-types. It can handle
-matching mime-types against a list of media-ranges. See section 14.1 of
-the HTTP specification [RFC 2616] for a complete explaination.
-
- http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
-
-Contents:
- - parse_mime_type(): Parses a mime-type into it's component parts.
- - parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
- - quality(): Determines the quality ('q') of a mime-type when compared against a list of media-ranges.
- - quality_parsed(): Just like quality() except the second parameter must be pre-parsed.
- - best_match(): Choose the mime-type with the highest quality ('q') from a list of candidates.
- - desired_matches(): Provide a list in order of server-desired priorities from a list of candidates.
-"""
-
-__version__ = "0.1.1"
-__author__ = 'Joe Gregorio'
-__email__ = "joe@bitworking.org"
-__credits__ = ""
-
-def parse_mime_type(mime_type):
- """Carves up a mime_type and returns a tuple of the
- (type, subtype, params) where 'params' is a dictionary
- of all the parameters for the media range.
- For example, the media range 'application/xhtml;q=0.5' would
- get parsed into:
-
- ('application', 'xhtml', {'q', '0.5'})
- """
- parts = mime_type.split(";")
- params = dict([tuple([s.strip() for s in param.split("=")])\
- for param in parts[1:] ])
- (type, subtype) = parts[0].split("/")
- return (type.strip(), subtype.strip(), params)
-
-def parse_media_range(range):
- """Carves up a media range and returns a tuple of the
- (type, subtype, params) where 'params' is a dictionary
- of all the parameters for the media range.
-
- For example, the media range ``application/*;q=0.5`` would
- get parsed into::
-
- ('application', '*', {'q', '0.5'})
-
- In addition this function also guarantees that there
- is a value for 'q' in the params dictionary, filling it
- in with a proper default if necessary.
- """
- (type, subtype, params) = parse_mime_type(range)
- if not params.has_key('q') or not params['q'] or \
- not float(params['q']) or float(params['q']) > 1\
- or float(params['q']) < 0:
- params['q'] = '1'
- return (type, subtype, params)
-
-def quality_parsed(mime_type, parsed_ranges):
- """Find the best match for a given mime_type against
- a list of media_ranges that have already been
- parsed by parse_media_range(). Returns the
- 'q' quality parameter of the best match, 0 if no
- match was found. This function bahaves the same as quality()
- except that 'parsed_ranges' must be a list of
- parsed media ranges. """
- best_fitness = -1
- best_match = ""
- best_fit_q = 0
- (target_type, target_subtype, target_params) =\
- parse_media_range(mime_type)
- for (type, subtype, params) in parsed_ranges:
- param_matches = sum([1 for (key, value) in \
- target_params.iteritems() if key != 'q' and \
- params.has_key(key) and value == params[key]])
- if (type == target_type or type == '*') and \
- (subtype == target_subtype or subtype == "*"):
- fitness = (type == target_type) and 100 or 0
- fitness += (subtype == target_subtype) and 10 or 0
- fitness += param_matches
- if fitness > best_fitness:
- best_fitness = fitness
- best_fit_q = params['q']
-
- return float(best_fit_q)
-
-def quality(mime_type, ranges):
- """Returns the quality 'q' of a mime_type when compared
- against the media-ranges in ranges. For example:
-
- >>> quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
- 0.7
-
- """
- parsed_ranges = [parse_media_range(r) for r in ranges.split(",")]
- return quality_parsed(mime_type, parsed_ranges)
-
-def best_match(supported, header):
- """Takes a list of supported mime-types and finds the best
- match for all the media-ranges listed in header. The value of
- header must be a string that conforms to the format of the
- HTTP Accept: header. The value of 'supported' is a list of
- mime-types.
-
- >>> best_match(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1')
- 'text/xml'
- """
- parsed_header = [parse_media_range(r) for r in header.split(",")]
- weighted_matches = [(quality_parsed(mime_type, parsed_header), mime_type)\
- for mime_type in supported]
- weighted_matches.sort()
- return weighted_matches[-1][0] and weighted_matches[-1][1] or ''
-
-def desired_matches(desired, header):
- """Takes a list of desired mime-types in the order the server prefers to
- send them regardless of the browsers preference.
-
- Browsers (such as Firefox) technically want XML over HTML depending on how
- one reads the specification. This function is provided for a server to
- declare a set of desired mime-types it supports, and returns a subset of
- the desired list in the same order should each one be Accepted by the
- browser.
-
- >>> sorted_match(['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']
- >>> sorted_match(['text/html', 'application/xml'], 'application/xml,application/json')
- ['application/xml']
- """
- matches = []
- parsed_ranges = [parse_media_range(r) for r in header.split(",")]
- for mimetype in desired:
- if quality_parsed(mimetype, parsed_ranges):
- matches.append(mimetype)
- return matches
-
-if __name__ == "__main__":
- import unittest
-
- class TestMimeParsing(unittest.TestCase):
-
- def test_parse_media_range(self):
- self.assert_(('application', 'xml', {'q': '1'}) == parse_media_range('application/xml;q=1'))
- self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml'))
- self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml;q='))
- self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml ; q='))
- self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=1;b=other'))
- self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=2;b=other'))
-
- def test_rfc_2616_example(self):
- accept = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"
- self.assertEqual(1, quality("text/html;level=1", accept))
- self.assertEqual(0.7, quality("text/html", accept))
- self.assertEqual(0.3, quality("text/plain", accept))
- self.assertEqual(0.5, quality("image/jpeg", accept))
- self.assertEqual(0.4, quality("text/html;level=2", accept))
- self.assertEqual(0.7, quality("text/html;level=3", accept))
-
- def test_best_match(self):
- mime_types_supported = ['application/xbel+xml', 'application/xml']
- # direct match
- self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml'), 'application/xbel+xml')
- # direct match with a q parameter
- self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml; q=1'), 'application/xbel+xml')
- # direct match of our second choice with a q parameter
- self.assertEqual(best_match(mime_types_supported, 'application/xml; q=1'), 'application/xml')
- # match using a subtype wildcard
- self.assertEqual(best_match(mime_types_supported, 'application/*; q=1'), 'application/xml')
- # match using a type wildcard
- self.assertEqual(best_match(mime_types_supported, '*/*'), 'application/xml')
-
- mime_types_supported = ['application/xbel+xml', 'text/xml']
- # match using a type versus a lower weighted subtype
- self.assertEqual(best_match(mime_types_supported, 'text/*;q=0.5,*/*; q=0.1'), 'text/xml')
- # fail to match anything
- self.assertEqual(best_match(mime_types_supported, 'text/html,application/atom+xml; q=0.9'), '')
-
- unittest.main()
diff --git a/lib/paste/util/multidict.py b/lib/paste/util/multidict.py
@@ -1,397 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-import cgi
-import copy
-import sys
-from UserDict import DictMixin
-
-class MultiDict(DictMixin):
-
- """
- An ordered dictionary that can have multiple values for each key.
- Adds the methods getall, getone, mixed, and add to the normal
- dictionary interface.
- """
-
- def __init__(self, *args, **kw):
- if len(args) > 1:
- raise TypeError(
- "MultiDict can only be called with one positional argument")
- if args:
- if hasattr(args[0], 'iteritems'):
- items = list(args[0].iteritems())
- elif hasattr(args[0], 'items'):
- items = args[0].items()
- else:
- items = list(args[0])
- self._items = items
- else:
- self._items = []
- self._items.extend(kw.iteritems())
-
- def __getitem__(self, key):
- for k, v in self._items:
- if k == key:
- return v
- raise KeyError(repr(key))
-
- def __setitem__(self, key, value):
- try:
- del self[key]
- except KeyError:
- pass
- self._items.append((key, value))
-
- def add(self, key, value):
- """
- Add the key and value, not overwriting any previous value.
- """
- self._items.append((key, value))
-
- def getall(self, key):
- """
- Return a list of all values matching the key (may be an empty list)
- """
- result = []
- for k, v in self._items:
- if key == k:
- result.append(v)
- return result
-
- def getone(self, key):
- """
- Get one value matching the key, raising a KeyError if multiple
- values were found.
- """
- v = self.getall(key)
- if not v:
- raise KeyError('Key not found: %r' % key)
- if len(v) > 1:
- raise KeyError('Multiple values match %r: %r' % (key, v))
- return v[0]
-
- def mixed(self):
- """
- Returns a dictionary where the values are either single
- values, or a list of values when a key/value appears more than
- once in this dictionary. This is similar to the kind of
- dictionary often used to represent the variables in a web
- request.
- """
- result = {}
- multi = {}
- for key, value in self._items:
- if key in result:
- # We do this to not clobber any lists that are
- # *actual* values in this dictionary:
- if key in multi:
- result[key].append(value)
- else:
- result[key] = [result[key], value]
- multi[key] = None
- else:
- result[key] = value
- return result
-
- def dict_of_lists(self):
- """
- Returns a dictionary where each key is associated with a
- list of values.
- """
- result = {}
- for key, value in self._items:
- if key in result:
- result[key].append(value)
- else:
- result[key] = [value]
- return result
-
- def __delitem__(self, key):
- items = self._items
- found = False
- for i in range(len(items)-1, -1, -1):
- if items[i][0] == key:
- del items[i]
- found = True
- if not found:
- raise KeyError(repr(key))
-
- def __contains__(self, key):
- for k, v in self._items:
- if k == key:
- return True
- return False
-
- has_key = __contains__
-
- def clear(self):
- self._items = []
-
- def copy(self):
- return MultiDict(self)
-
- def setdefault(self, key, default=None):
- for k, v in self._items:
- if key == k:
- return v
- self._items.append((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))
- for i in range(len(self._items)):
- if self._items[i][0] == key:
- v = self._items[i][1]
- del self._items[i]
- return v
- if args:
- return args[0]
- else:
- raise KeyError(repr(key))
-
- def popitem(self):
- return self._items.pop()
-
- def update(self, other=None, **kwargs):
- if other is None:
- pass
- elif hasattr(other, 'items'):
- self._items.extend(other.items())
- elif hasattr(other, 'keys'):
- for k in other.keys():
- self._items.append((k, other[k]))
- else:
- for k, v in other:
- self._items.append((k, v))
- if kwargs:
- self.update(kwargs)
-
- def __repr__(self):
- items = ', '.join(['(%r, %r)' % v for v in self._items])
- return '%s([%s])' % (self.__class__.__name__, items)
-
- def __len__(self):
- return len(self._items)
-
- ##
- ## All the iteration:
- ##
-
- def keys(self):
- return [k for k, v in self._items]
-
- def iterkeys(self):
- for k, v in self._items:
- yield k
-
- __iter__ = iterkeys
-
- def items(self):
- return self._items[:]
-
- def iteritems(self):
- return iter(self._items)
-
- def values(self):
- return [v for k, v in self._items]
-
- def itervalues(self):
- for k, v in self._items:
- yield v
-
-class UnicodeMultiDict(DictMixin):
- """
- A MultiDict wrapper that decodes returned values to unicode on the
- fly. Decoding is not applied to assigned values.
-
- The key/value contents are assumed to be ``str``/``strs`` or
- ``str``/``FieldStorages`` (as is returned by the ``paste.request.parse_``
- functions).
-
- Can optionally also decode keys when the ``decode_keys`` argument is
- True.
-
- ``FieldStorage`` instances are cloned, and the clone's ``filename``
- variable is decoded. Its ``name`` variable is decoded when ``decode_keys``
- is enabled.
-
- """
- def __init__(self, multi=None, encoding=None, errors='strict',
- decode_keys=False):
- self.multi = multi
- if encoding is None:
- encoding = sys.getdefaultencoding()
- self.encoding = encoding
- self.errors = errors
- self.decode_keys = decode_keys
-
- def _decode_key(self, key):
- if self.decode_keys:
- try:
- key = key.decode(self.encoding, self.errors)
- except AttributeError:
- pass
- return key
-
- def _decode_value(self, value):
- """
- Decode the specified value to unicode. Assumes value is a ``str`` or
- `FieldStorage`` object.
-
- ``FieldStorage`` objects are specially handled.
- """
- if isinstance(value, cgi.FieldStorage):
- # decode FieldStorage's field name and filename
- value = copy.copy(value)
- if self.decode_keys:
- value.name = value.name.decode(self.encoding, self.errors)
- value.filename = value.filename.decode(self.encoding, self.errors)
- else:
- try:
- value = value.decode(self.encoding, self.errors)
- except AttributeError:
- pass
- return value
-
- def __getitem__(self, key):
- return self._decode_value(self.multi.__getitem__(key))
-
- def __setitem__(self, key, value):
- self.multi.__setitem__(key, value)
-
- def add(self, key, value):
- """
- Add the key and value, not overwriting any previous value.
- """
- self.multi.add(key, value)
-
- def getall(self, key):
- """
- Return a list of all values matching the key (may be an empty list)
- """
- return [self._decode_value(v) for v in self.multi.getall(key)]
-
- def getone(self, key):
- """
- Get one value matching the key, raising a KeyError if multiple
- values were found.
- """
- return self._decode_value(self.multi.getone(key))
-
- def mixed(self):
- """
- Returns a dictionary where the values are either single
- values, or a list of values when a key/value appears more than
- once in this dictionary. This is similar to the kind of
- dictionary often used to represent the variables in a web
- request.
- """
- unicode_mixed = {}
- for key, value in self.multi.mixed().iteritems():
- if isinstance(value, list):
- value = [self._decode_value(value) for value in value]
- else:
- value = self._decode_value(value)
- unicode_mixed[self._decode_key(key)] = value
- return unicode_mixed
-
- def dict_of_lists(self):
- """
- Returns a dictionary where each key is associated with a
- list of values.
- """
- unicode_dict = {}
- for key, value in self.multi.dict_of_lists().iteritems():
- value = [self._decode_value(value) for value in value]
- unicode_dict[self._decode_key(key)] = value
- return unicode_dict
-
- def __delitem__(self, key):
- self.multi.__delitem__(key)
-
- def __contains__(self, key):
- return self.multi.__contains__(key)
-
- has_key = __contains__
-
- def clear(self):
- self.multi.clear()
-
- def copy(self):
- return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors)
-
- def setdefault(self, key, default=None):
- return self._decode_value(self.multi.setdefault(key, default))
-
- def pop(self, key, *args):
- return self._decode_value(self.multi.pop(key, *args))
-
- def popitem(self):
- k, v = self.multi.popitem()
- return (self._decode_key(k), self._decode_value(v))
-
- def __repr__(self):
- items = ', '.join(['(%r, %r)' % v for v in self.items()])
- return '%s([%s])' % (self.__class__.__name__, items)
-
- def __len__(self):
- return self.multi.__len__()
-
- ##
- ## All the iteration:
- ##
-
- def keys(self):
- return [self._decode_key(k) for k in self.multi.iterkeys()]
-
- def iterkeys(self):
- for k in self.multi.iterkeys():
- yield self._decode_key(k)
-
- __iter__ = iterkeys
-
- def items(self):
- return [(self._decode_key(k), self._decode_value(v)) for \
- k, v in self.multi.iteritems()]
-
- def iteritems(self):
- for k, v in self.multi.iteritems():
- yield (self._decode_key(k), self._decode_value(v))
-
- def values(self):
- return [self._decode_value(v) for v in self.multi.itervalues()]
-
- def itervalues(self):
- for v in self.multi.itervalues():
- yield self._decode_value(v)
-
-__test__ = {
- 'general': """
- >>> d = MultiDict(a=1, b=2)
- >>> d['a']
- 1
- >>> d.getall('c')
- []
- >>> d.add('a', 2)
- >>> d['a']
- 1
- >>> d.getall('a')
- [1, 2]
- >>> d['b'] = 4
- >>> d.getall('b')
- [4]
- >>> d.keys()
- ['a', 'a', 'b']
- >>> d.items()
- [('a', 1), ('a', 2), ('b', 4)]
- >>> d.mixed()
- {'a': [1, 2], 'b': 4}
- >>> MultiDict([('a', 'b')], c=2)
- MultiDict([('a', 'b'), ('c', 2)])
- """}
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/lib/paste/util/quoting.py b/lib/paste/util/quoting.py
@@ -1,80 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-import cgi
-import htmlentitydefs
-import urllib
-import re
-
-__all__ = ['html_quote', 'html_unquote', 'url_quote', 'url_unquote',
- 'strip_html']
-
-default_encoding = 'UTF-8'
-
-def html_quote(v, encoding=None):
- r"""
- Quote the value (turned to a string) as HTML. This quotes <, >,
- and quotes:
-
- >>> html_quote(1)
- '1'
- >>> html_quote(None)
- ''
- >>> html_quote('<hey!>')
- '<hey!>'
- >>> html_quote(u'\u1029')
- '\xe1\x80\xa9'
- """
- encoding = encoding or default_encoding
- if v is None:
- return ''
- elif isinstance(v, str):
- return cgi.escape(v, 1)
- elif isinstance(v, unicode):
- return cgi.escape(v.encode(encoding), 1)
- else:
- return cgi.escape(unicode(v).encode(encoding), 1)
-
-_unquote_re = re.compile(r'&([a-zA-Z]+);')
-def _entity_subber(match, name2c=htmlentitydefs.name2codepoint):
- code = name2c.get(match.group(1))
- if code:
- return unichr(code)
- else:
- return match.group(0)
-
-def html_unquote(s, encoding=None):
- r"""
- Decode the value.
-
- >>> html_unquote('<hey you>')
- u'<hey\xa0you>'
- >>> html_unquote('')
- u''
- >>> html_unquote('&blahblah;')
- u'&blahblah;'
- >>> html_unquote('\xe1\x80\xa9')
- u'\u1029'
- """
- if isinstance(s, str):
- s = s.decode(encoding or default_encoding)
- return _unquote_re.sub(_entity_subber, s)
-
-def strip_html(s):
- # should this use html_unquote?
- s = re.sub('<.*?>', '', s)
- s = html_unquote(s)
- return s
-
-def no_quote(s):
- """
- Quoting that doesn't do anything
- """
- return s
-
-url_quote = urllib.quote
-url_unquote = urllib.unquote
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/lib/paste/util/scgiserver.py b/lib/paste/util/scgiserver.py
@@ -1,172 +0,0 @@
-#! /usr/bin/env python
-"""
-SCGI-->WSGI application proxy, "SWAP".
-
-(Originally written by Titus Brown.)
-
-This lets an SCGI front-end like mod_scgi be used to execute WSGI
-application objects. To use it, subclass the SWAP class like so::
-
- class TestAppHandler(swap.SWAP):
- def __init__(self, *args, **kwargs):
- self.prefix = '/canal'
- self.app_obj = TestAppClass
- swap.SWAP.__init__(self, *args, **kwargs)
-
-where 'TestAppClass' is the application object from WSGI and '/canal'
-is the prefix for what is served by the SCGI Web-server-side process.
-
-Then execute the SCGI handler "as usual" by doing something like this::
-
- scgi_server.SCGIServer(TestAppHandler, port=4000).serve()
-
-and point mod_scgi (or whatever your SCGI front end is) at port 4000.
-
-Kudos to the WSGI folk for writing a nice PEP & the Quixote folk for
-writing a nice extensible SCGI server for Python!
-"""
-
-import sys
-import time
-from scgi import scgi_server
-
-def debug(msg):
- timestamp = time.strftime("%Y-%m-%d %H:%M:%S",
- time.localtime(time.time()))
- sys.stderr.write("[%s] %s\n" % (timestamp, msg))
-
-class SWAP(scgi_server.SCGIHandler):
- """
- SCGI->WSGI application proxy: let an SCGI server execute WSGI
- application objects.
- """
- 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"
- args = (self,) + args
- scgi_server.SCGIHandler.__init__(*args, **kwargs)
-
- def handle_connection(self, conn):
- """
- Handle an individual connection.
- """
- input = conn.makefile("r")
- output = conn.makefile("w")
-
- environ = self.read_env(input)
- environ['wsgi.input'] = input
- environ['wsgi.errors'] = sys.stderr
- environ['wsgi.version'] = (1, 0)
- environ['wsgi.multithread'] = False
- environ['wsgi.multiprocess'] = True
- environ['wsgi.run_once'] = False
-
- # dunno how SCGI does HTTPS signalling; can't test it myself... @CTB
- if environ.get('HTTPS','off') in ('on','1'):
- environ['wsgi.url_scheme'] = 'https'
- else:
- environ['wsgi.url_scheme'] = 'http'
-
- ## SCGI does some weird environ manglement. We need to set
- ## SCRIPT_NAME from 'prefix' and then set PATH_INFO from
- ## REQUEST_URI.
-
- prefix = self.prefix
- path = environ['REQUEST_URI'][len(prefix):].split('?', 1)[0]
-
- environ['SCRIPT_NAME'] = prefix
- environ['PATH_INFO'] = path
-
- headers_set = []
- headers_sent = []
- chunks = []
- def write(data):
- chunks.append(data)
-
- def start_response(status, response_headers, exc_info=None):
- if exc_info:
- try:
- if headers_sent:
- # Re-raise original exception if headers sent
- raise exc_info[0], exc_info[1], exc_info[2]
- finally:
- exc_info = None # avoid dangling circular ref
- elif headers_set:
- raise AssertionError("Headers already set!")
-
- headers_set[:] = [status, response_headers]
- return write
-
- ###
-
- result = self.app_obj(environ, start_response)
- 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
- status = '500 Server Error'
- response_headers = [('Content-type', 'text/html')]
- 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)
- output.write('\r\n')
-
- for data in chunks:
- output.write(data)
- finally:
- if hasattr(result,'close'):
- result.close()
-
- # SCGI backends use connection closing to signal 'fini'.
- try:
- input.close()
- output.close()
- conn.close()
- except IOError, err:
- debug("IOError while closing connection ignored: %s" % err)
-
-
-def serve_application(application, prefix, port=None, host=None, max_children=None):
- """
- Serve the specified WSGI application via SCGI proxy.
-
- ``application``
- The WSGI application to serve.
-
- ``prefix``
- The prefix for what is served by the SCGI Web-server-side process.
-
- ``port``
- Optional port to bind the SCGI proxy to. Defaults to SCGIServer's
- default port value.
-
- ``host``
- Optional host to bind the SCGI proxy to. Defaults to SCGIServer's
- default host value.
-
- ``host``
- Optional maximum number of child processes the SCGIServer will
- spawn. Defaults to SCGIServer's default max_children value.
- """
- class SCGIAppHandler(SWAP):
- def __init__ (self, *args, **kwargs):
- self.prefix = prefix
- self.app_obj = application
- SWAP.__init__(self, *args, **kwargs)
-
- kwargs = dict(handler_class=SCGIAppHandler)
- for kwarg in ('host', 'port', 'max_children'):
- if locals()[kwarg] is not None:
- kwargs[kwarg] = locals()[kwarg]
-
- scgi_server.SCGIServer(**kwargs).serve()
diff --git a/lib/paste/util/string24.py b/lib/paste/util/string24.py
@@ -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/lib/paste/util/subprocess24.py b/lib/paste/util/subprocess24.py
@@ -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/lib/paste/util/template.py b/lib/paste/util/template.py
@@ -1,758 +0,0 @@
-"""
-A small templating language
-
-This implements a small templating language for use internally in
-Paste and Paste Script. This language implements if/elif/else,
-for/continue/break, expressions, and blocks of Python code. The
-syntax is::
-
- {{any expression (function calls etc)}}
- {{any expression | filter}}
- {{for x in y}}...{{endfor}}
- {{if x}}x{{elif y}}y{{else}}z{{endif}}
- {{py:x=1}}
- {{py:
- def foo(bar):
- return 'baz'
- }}
- {{default var = default_value}}
- {{# comment}}
-
-You use this with the ``Template`` class or the ``sub`` shortcut.
-The ``Template`` class takes the template string and the name of
-the template (for errors) and a default namespace. Then (like
-``string.Template``) you can call the ``tmpl.substitute(**kw)``
-method to make a substitution (or ``tmpl.substitute(a_dict)``).
-
-``sub(content, **kw)`` substitutes the template immediately. You
-can use ``__name='tmpl.html'`` to set the name of the template.
-
-If there are syntax errors ``TemplateError`` will be raised.
-"""
-
-import re
-import sys
-import cgi
-import urllib
-from paste.util.looper import looper
-
-__all__ = ['TemplateError', 'Template', 'sub', 'HTMLTemplate',
- 'sub_html', 'html', 'bunch']
-
-token_re = re.compile(r'\{\{|\}\}')
-in_re = re.compile(r'\s+in\s+')
-var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)
-
-class TemplateError(Exception):
- """Exception raised while parsing a template
- """
-
- def __init__(self, message, position, name=None):
- self.message = message
- self.position = position
- self.name = name
-
- def __str__(self):
- msg = '%s at line %s column %s' % (
- self.message, self.position[0], self.position[1])
- if self.name:
- msg += ' in %s' % self.name
- return msg
-
-class _TemplateContinue(Exception):
- pass
-
-class _TemplateBreak(Exception):
- pass
-
-class Template(object):
-
- default_namespace = {
- 'start_braces': '{{',
- 'end_braces': '}}',
- 'looper': looper,
- }
-
- default_encoding = 'utf8'
-
- def __init__(self, content, name=None, namespace=None):
- self.content = content
- self._unicode = isinstance(content, unicode)
- self.name = name
- self._parsed = parse(content, name=name)
- if namespace is None:
- namespace = {}
- self.namespace = namespace
-
- def from_filename(cls, filename, namespace=None, encoding=None):
- f = open(filename, 'rb')
- c = f.read()
- f.close()
- if encoding:
- c = c.decode(encoding)
- return cls(content=c, name=filename, namespace=namespace)
-
- from_filename = classmethod(from_filename)
-
- def __repr__(self):
- return '<%s %s name=%r>' % (
- self.__class__.__name__,
- hex(id(self))[2:], self.name)
-
- def substitute(self, *args, **kw):
- if args:
- if kw:
- raise TypeError(
- "You can only give positional *or* keyword arguments")
- if len(args) > 1:
- raise TypeError(
- "You can only give on positional argument")
- kw = args[0]
- ns = self.default_namespace.copy()
- ns.update(self.namespace)
- ns.update(kw)
- result = self._interpret(ns)
- return result
-
- def _interpret(self, ns):
- __traceback_hide__ = True
- parts = []
- self._interpret_codes(self._parsed, ns, out=parts)
- return ''.join(parts)
-
- def _interpret_codes(self, codes, ns, out):
- __traceback_hide__ = True
- for item in codes:
- if isinstance(item, basestring):
- out.append(item)
- else:
- self._interpret_code(item, ns, out)
-
- def _interpret_code(self, code, ns, out):
- __traceback_hide__ = True
- name, pos = code[0], code[1]
- if name == 'py':
- self._exec(code[2], ns, pos)
- elif name == 'continue':
- raise _TemplateContinue()
- elif name == 'break':
- raise _TemplateBreak()
- elif name == 'for':
- vars, expr, content = code[2], code[3], code[4]
- expr = self._eval(expr, ns, pos)
- self._interpret_for(vars, expr, content, ns, out)
- elif name == 'cond':
- parts = code[2:]
- self._interpret_if(parts, ns, out)
- elif name == 'expr':
- parts = code[2].split('|')
- base = self._eval(parts[0], ns, pos)
- for part in parts[1:]:
- func = self._eval(part, ns, pos)
- base = func(base)
- out.append(self._repr(base, pos))
- elif name == 'default':
- var, expr = code[2], code[3]
- if var not in ns:
- result = self._eval(expr, ns, pos)
- ns[var] = result
- elif name == 'comment':
- return
- else:
- assert 0, "Unknown code: %r" % name
-
- def _interpret_for(self, vars, expr, content, ns, out):
- __traceback_hide__ = True
- for item in expr:
- if len(vars) == 1:
- ns[vars[0]] = item
- else:
- if len(vars) != len(item):
- raise ValueError(
- 'Need %i items to unpack (got %i items)'
- % (len(vars), len(item)))
- for name, value in zip(vars, item):
- ns[name] = value
- try:
- self._interpret_codes(content, ns, out)
- except _TemplateContinue:
- continue
- except _TemplateBreak:
- break
-
- def _interpret_if(self, parts, ns, out):
- __traceback_hide__ = True
- # @@: if/else/else gets through
- for part in parts:
- assert not isinstance(part, basestring)
- name, pos = part[0], part[1]
- if name == 'else':
- result = True
- else:
- result = self._eval(part[2], ns, pos)
- if result:
- self._interpret_codes(part[3], ns, out)
- break
-
- def _eval(self, code, ns, pos):
- __traceback_hide__ = True
- try:
- value = eval(code, ns)
- return value
- except:
- exc_info = sys.exc_info()
- e = exc_info[1]
- if getattr(e, 'args'):
- arg0 = e.args[0]
- else:
- arg0 = str(e)
- e.args = (self._add_line_info(arg0, pos),)
- raise exc_info[0], e, exc_info[2]
-
- def _exec(self, code, ns, pos):
- __traceback_hide__ = True
- try:
- exec code in ns
- except:
- exc_info = sys.exc_info()
- e = exc_info[1]
- e.args = (self._add_line_info(e.args[0], pos),)
- raise exc_info[0], e, exc_info[2]
-
- def _repr(self, value, pos):
- __traceback_hide__ = True
- try:
- if value is None:
- return ''
- if self._unicode:
- try:
- value = unicode(value)
- except UnicodeDecodeError:
- value = str(value)
- else:
- value = str(value)
- except:
- exc_info = sys.exc_info()
- e = exc_info[1]
- e.args = (self._add_line_info(e.args[0], pos),)
- raise exc_info[0], e, exc_info[2]
- else:
- if self._unicode and isinstance(value, str):
- 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):
- 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" % (
- msg, pos[0], pos[1])
- if self.name:
- msg += " in file %s" % self.name
- return msg
-
-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)
- return tmpl.substitute(vars)
-
-class bunch(dict):
-
- def __init__(self, **kw):
- for name, value in kw.items():
- setattr(self, name, value)
-
- def __setattr__(self, name, value):
- self[name] = value
-
- def __getattr__(self, name):
- try:
- return self[name]
- except KeyError:
- raise AttributeError(name)
-
- def __getitem__(self, key):
- if 'default' in self:
- try:
- return dict.__getitem__(self, key)
- except KeyError:
- return dict.__getitem__(self, 'default')
- else:
- return dict.__getitem__(self, key)
-
- def __repr__(self):
- items = [
- (k, v) for k, v in self.items()]
- items.sort()
- return '<%s %s>' % (
- self.__class__.__name__,
- ' '.join(['%s=%r' % (k, v) for k, v in items]))
-
-############################################################
-## HTML Templating
-############################################################
-
-class html(object):
- def __init__(self, value):
- self.value = value
- def __str__(self):
- return self.value
- def __repr__(self):
- return '<%s %r>' % (
- self.__class__.__name__, self.value)
-
-def html_quote(value):
- if value is None:
- return ''
- if not isinstance(value, basestring):
- if hasattr(value, '__unicode__'):
- value = unicode(value)
- else:
- value = str(value)
- value = cgi.escape(value, 1)
- if isinstance(value, unicode):
- value = value.encode('ascii', 'xmlcharrefreplace')
- return value
-
-def url(v):
- if not isinstance(v, basestring):
- if hasattr(v, '__unicode__'):
- v = unicode(v)
- else:
- v = str(v)
- if isinstance(v, unicode):
- v = v.encode('utf8')
- return urllib.quote(v)
-
-def attr(**kw):
- kw = kw.items()
- kw.sort()
- parts = []
- for name, value in kw:
- if value is None:
- continue
- if name.endswith('_'):
- name = name[:-1]
- parts.append('%s="%s"' % (html_quote(name), html_quote(value)))
- return html(' '.join(parts))
-
-class HTMLTemplate(Template):
-
- default_namespace = Template.default_namespace.copy()
- default_namespace.update(dict(
- html=html,
- attr=attr,
- url=url,
- ))
-
- def _repr(self, value, pos):
- plain = Template._repr(self, value, pos)
- if isinstance(value, html):
- return plain
- else:
- return html_quote(plain)
-
-def sub_html(content, **kw):
- name = kw.get('__name')
- tmpl = HTMLTemplate(content, name=name)
- return tmpl.substitute(kw)
- return result
-
-
-############################################################
-## Lexing and Parsing
-############################################################
-
-def lex(s, name=None, trim_whitespace=True):
- """
- Lex a string into chunks:
-
- >>> lex('hey')
- ['hey']
- >>> lex('hey {{you}}')
- ['hey ', ('you', (1, 7))]
- >>> lex('hey {{')
- Traceback (most recent call last):
- ...
- TemplateError: No }} to finish last expression at line 1 column 7
- >>> lex('hey }}')
- Traceback (most recent call last):
- ...
- TemplateError: }} outside expression at line 1 column 7
- >>> lex('hey {{ {{')
- Traceback (most recent call last):
- ...
- TemplateError: {{ inside expression at line 1 column 10
-
- """
- in_expr = False
- chunks = []
- last = 0
- last_pos = (1, 1)
- for match in token_re.finditer(s):
- expr = match.group(0)
- pos = find_position(s, match.end())
- if expr == '{{' and in_expr:
- raise TemplateError('{{ inside expression', position=pos,
- name=name)
- elif expr == '}}' and not in_expr:
- raise TemplateError('}} outside expression', position=pos,
- name=name)
- if expr == '{{':
- part = s[last:match.start()]
- if part:
- chunks.append(part)
- in_expr = True
- else:
- chunks.append((s[last:match.start()], last_pos))
- in_expr = False
- last = match.end()
- last_pos = pos
- if in_expr:
- raise TemplateError('No }} to finish last expression',
- name=name, position=last_pos)
- part = s[last:]
- if part:
- chunks.append(part)
- if trim_whitespace:
- chunks = trim_lex(chunks)
- return chunks
-
-statement_re = re.compile(r'^(?:if |elif |else |for |py:)')
-single_statements = ['endif', 'endfor', 'continue', 'break']
-trail_whitespace_re = re.compile(r'\n[\t ]*$')
-lead_whitespace_re = re.compile(r'^[\t ]*\n')
-
-def trim_lex(tokens):
- r"""
- Takes a lexed set of tokens, and removes whitespace when there is
- a directive on a line by itself:
-
- >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
- >>> tokens
- [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
- >>> trim_lex(tokens)
- [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
- """
- for i in range(len(tokens)):
- current = tokens[i]
- if isinstance(tokens[i], basestring):
- # we don't trim this
- continue
- item = current[0]
- if not statement_re.search(item) and item not in single_statements:
- continue
- if not i:
- prev = ''
- else:
- prev = tokens[i-1]
- if i+1 >= len(tokens):
- next = ''
- else:
- next = tokens[i+1]
- if (not isinstance(next, basestring)
- or not isinstance(prev, basestring)):
- continue
- if ((not prev or trail_whitespace_re.search(prev))
- and (not next or lead_whitespace_re.search(next))):
- if prev:
- m = trail_whitespace_re.search(prev)
- # +1 to leave the leading \n on:
- prev = prev[:m.start()+1]
- tokens[i-1] = prev
- if next:
- m = lead_whitespace_re.search(next)
- next = next[m.end():]
- tokens[i+1] = next
- return tokens
-
-
-def find_position(string, index):
- """Given a string and index, return (line, column)"""
- leading = string[:index].splitlines()
- return (len(leading), len(leading[-1])+1)
-
-def parse(s, name=None):
- r"""
- Parses a string into a kind of AST
-
- >>> parse('{{x}}')
- [('expr', (1, 3), 'x')]
- >>> parse('foo')
- ['foo']
- >>> parse('{{if x}}test{{endif}}')
- [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
- >>> parse('series->{{for x in y}}x={{x}}{{endfor}}')
- ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
- >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
- [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
- >>> parse('{{py:x=1}}')
- [('py', (1, 3), 'x=1')]
- >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}')
- [('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):
- ...
- TemplateError: continue outside of for loop at line 1 column 3
- >>> parse('{{if x}}foo')
- Traceback (most recent call last):
- ...
- TemplateError: No {{endif}} at line 1 column 3
- >>> parse('{{else}}')
- Traceback (most recent call last):
- ...
- TemplateError: else outside of an if block at line 1 column 3
- >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
- Traceback (most recent call last):
- ...
- TemplateError: Unexpected endif at line 1 column 25
- >>> parse('{{if}}{{endif}}')
- Traceback (most recent call last):
- ...
- TemplateError: if with no expression at line 1 column 3
- >>> parse('{{for x y}}{{endfor}}')
- Traceback (most recent call last):
- ...
- TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
- >>> parse('{{py:x=1\ny=2}}')
- Traceback (most recent call last):
- ...
- TemplateError: Multi-line py blocks must start with a newline at line 1 column 3
- """
- tokens = lex(s, name=name)
- result = []
- while tokens:
- next, tokens = parse_expr(tokens, name)
- result.append(next)
- return result
-
-def parse_expr(tokens, name, context=()):
- if isinstance(tokens[0], basestring):
- return tokens[0], tokens[1:]
- expr, pos = tokens[0]
- expr = expr.strip()
- if expr.startswith('py:'):
- expr = expr[3:].lstrip(' \t')
- if expr.startswith('\n'):
- expr = expr[1:]
- else:
- if '\n' in expr:
- raise TemplateError(
- 'Multi-line py blocks must start with a newline',
- position=pos, name=name)
- return ('py', pos, expr), tokens[1:]
- elif expr in ('continue', 'break'):
- if 'for' not in context:
- raise TemplateError(
- 'continue outside of for loop',
- position=pos, name=name)
- return (expr, pos), tokens[1:]
- elif expr.startswith('if '):
- return parse_cond(tokens, name, context)
- elif (expr.startswith('elif ')
- or expr == 'else'):
- raise TemplateError(
- '%s outside of an if block' % expr.split()[0],
- position=pos, name=name)
- elif expr in ('if', 'elif', 'for'):
- raise TemplateError(
- '%s with no expression' % expr,
- position=pos, name=name)
- elif expr in ('endif', 'endfor'):
- raise TemplateError(
- 'Unexpected %s' % expr,
- position=pos, name=name)
- elif expr.startswith('for '):
- return parse_for(tokens, name, context)
- elif expr.startswith('default '):
- return parse_default(tokens, name, context)
- elif expr.startswith('#'):
- return ('comment', pos, tokens[0][0]), tokens[1:]
- return ('expr', pos, tokens[0][0]), tokens[1:]
-
-def parse_cond(tokens, name, context):
- start = tokens[0][1]
- pieces = []
- context = context + ('if',)
- while 1:
- if not tokens:
- raise TemplateError(
- 'Missing {{endif}}',
- position=start, name=name)
- if (isinstance(tokens[0], tuple)
- and tokens[0][0] == 'endif'):
- return ('cond', start) + tuple(pieces), tokens[1:]
- next, tokens = parse_one_cond(tokens, name, context)
- pieces.append(next)
-
-def parse_one_cond(tokens, name, context):
- (first, pos), tokens = tokens[0], tokens[1:]
- content = []
- if first.endswith(':'):
- first = first[:-1]
- if first.startswith('if '):
- part = ('if', pos, first[3:].lstrip(), content)
- elif first.startswith('elif '):
- part = ('elif', pos, first[5:].lstrip(), content)
- elif first == 'else':
- part = ('else', pos, None, content)
- else:
- assert 0, "Unexpected token %r at %s" % (first, pos)
- while 1:
- if not tokens:
- raise TemplateError(
- 'No {{endif}}',
- position=pos, name=name)
- if (isinstance(tokens[0], tuple)
- and (tokens[0][0] == 'endif'
- or tokens[0][0].startswith('elif ')
- or tokens[0][0] == 'else')):
- 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:]
- context = ('for',) + context
- content = []
- assert first.startswith('for ')
- if first.endswith(':'):
- first = first[:-1]
- first = first[3:].strip()
- match = in_re.search(first)
- if not match:
- raise TemplateError(
- 'Bad for (no "in") in %r' % first,
- position=pos, name=name)
- vars = first[:match.start()]
- if '(' in vars:
- raise TemplateError(
- 'You cannot have () in the variable section of a for loop (%r)'
- % vars, position=pos, name=name)
- vars = tuple([
- v.strip() for v in first[:match.start()].split(',')
- if v.strip()])
- expr = first[match.end():]
- while 1:
- if not tokens:
- raise TemplateError(
- 'No {{endfor}}',
- position=pos, name=name)
- if (isinstance(tokens[0], tuple)
- and tokens[0][0] == 'endfor'):
- return ('for', pos, vars, expr, content), tokens[1:]
- next, tokens = parse_expr(tokens, name, context)
- content.append(next)
-
-def parse_default(tokens, name, context):
- first, pos = tokens[0]
- assert first.startswith('default ')
- first = first.split(None, 1)[1]
- parts = first.split('=', 1)
- if len(parts) == 1:
- raise TemplateError(
- "Expression must be {{default var=value}}; no = found in %r" % first,
- position=pos, name=name)
- var = parts[0].strip()
- if ',' in var:
- raise TemplateError(
- "{{default x, y = ...}} is not supported",
- position=pos, name=name)
- if not var_re.search(var):
- raise TemplateError(
- "Not a valid variable name for {{default}}: %r"
- % var, position=pos, name=name)
- expr = parts[1].strip()
- return ('default', pos, var, expr), tokens[1:]
-
-_fill_command_usage = """\
-%prog [OPTIONS] TEMPLATE arg=value
-
-Use py:arg=value to set a Python value; otherwise all values are
-strings.
-"""
-
-def fill_command(args=None):
- import sys, optparse, pkg_resources, os
- if args is None:
- args = sys.argv[1:]
- dist = pkg_resources.get_distribution('Paste')
- parser = optparse.OptionParser(
- version=str(dist),
- usage=_fill_command_usage)
- parser.add_option(
- '-o', '--output',
- dest='output',
- metavar="FILENAME",
- help="File to write output to (default stdout)")
- parser.add_option(
- '--html',
- dest='use_html',
- action='store_true',
- help="Use HTML style filling (including automatic HTML quoting)")
- parser.add_option(
- '--env',
- dest='use_env',
- action='store_true',
- help="Put the environment in as top-level variables")
- options, args = parser.parse_args(args)
- if len(args) < 1:
- print 'You must give a template filename'
- print dir(parser)
- assert 0
- template_name = args[0]
- args = args[1:]
- vars = {}
- if options.use_env:
- vars.update(os.environ)
- for value in args:
- if '=' not in value:
- print 'Bad argument: %r' % value
- sys.exit(2)
- name, value = value.split('=', 1)
- if name.startswith('py:'):
- name = name[:3]
- value = eval(value)
- vars[name] = value
- if template_name == '-':
- template_content = sys.stdin.read()
- template_name = '<stdin>'
- else:
- f = open(template_name, 'rb')
- template_content = f.read()
- f.close()
- if options.use_html:
- TemplateClass = HTMLTemplate
- else:
- TemplateClass = Template
- template = TemplateClass(template_content, name=template_name)
- result = template.substitute(vars)
- if options.output:
- f = open(options.output, 'wb')
- f.write(result)
- f.close()
- else:
- sys.stdout.write(result)
-
-if __name__ == '__main__':
- from paste.util.template import fill_command
- fill_command()
-
-
diff --git a/lib/paste/util/threadedprint.py b/lib/paste/util/threadedprint.py
@@ -1,250 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-threadedprint.py
-================
-
-:author: Ian Bicking
-:date: 12 Jul 2004
-
-Multi-threaded printing; allows the output produced via print to be
-separated according to the thread.
-
-To use this, you must install the catcher, like::
-
- threadedprint.install()
-
-The installation optionally takes one of three parameters:
-
-default
- The default destination for print statements (e.g., ``sys.stdout``).
-factory
- A function that will produce the stream for a thread, given the
- thread's name.
-paramwriter
- Instead of writing to a file-like stream, this function will be
- called like ``paramwriter(thread_name, text)`` for every write.
-
-The thread name is the value returned by
-``threading.currentThread().getName()``, a string (typically something
-like Thread-N).
-
-You can also submit file-like objects for specific threads, which will
-override any of these parameters. To do this, call ``register(stream,
-[threadName])``. ``threadName`` is optional, and if not provided the
-stream will be registered for the current thread.
-
-If no specific stream is registered for a thread, and no default has
-been provided, then an error will occur when anything is written to
-``sys.stdout`` (or printed).
-
-Note: the stream's ``write`` method will be called in the thread the
-text came from, so you should consider thread safety, especially if
-multiple threads share the same writer.
-
-Note: if you want access to the original standard out, use
-``sys.__stdout__``.
-
-You may also uninstall this, via::
-
- threadedprint.uninstall()
-
-TODO
-----
-
-* Something with ``sys.stderr``.
-* Some default handlers. Maybe something that hooks into `logging`.
-* Possibly cache the results of ``factory`` calls. This would be a
- semantic change.
-
-"""
-
-import threading
-import sys
-from paste.util import filemixin
-
-class PrintCatcher(filemixin.FileMixin):
-
- def __init__(self, default=None, factory=None, paramwriter=None,
- leave_stdout=False):
- assert len(filter(lambda x: x is not None,
- [default, factory, paramwriter])) <= 1, (
- "You can only provide one of default, factory, or paramwriter")
- if leave_stdout:
- assert not default, (
- "You cannot pass in both default (%r) and "
- "leave_stdout=True" % default)
- default = sys.stdout
- if default:
- self._defaultfunc = self._writedefault
- elif factory:
- self._defaultfunc = self._writefactory
- elif paramwriter:
- self._defaultfunc = self._writeparam
- else:
- self._defaultfunc = self._writeerror
- self._default = default
- self._factory = factory
- self._paramwriter = paramwriter
- self._catchers = {}
-
- def write(self, v, currentThread=threading.currentThread):
- name = currentThread().getName()
- catchers = self._catchers
- if not catchers.has_key(name):
- self._defaultfunc(name, v)
- else:
- catcher = catchers[name]
- catcher.write(v)
-
- def seek(self, *args):
- # Weird, but Google App Engine is seeking on stdout
- name = threading.currentThread().getName()
- catchers = self._catchers
- if not name in catchers:
- self._default.seek(*args)
- else:
- catchers[name].seek(*args)
-
- def read(self, *args):
- name = threading.currentThread().getName()
- catchers = self._catchers
- if not name in catchers:
- self._default.read(*args)
- else:
- catchers[name].read(*args)
-
-
- def _writedefault(self, name, v):
- self._default.write(v)
-
- def _writefactory(self, name, v):
- self._factory(name).write(v)
-
- def _writeparam(self, name, v):
- self._paramwriter(name, v)
-
- def _writeerror(self, name, v):
- assert False, (
- "There is no PrintCatcher output stream for the thread %r"
- % name)
-
- def register(self, catcher, name=None,
- currentThread=threading.currentThread):
- if name is None:
- name = currentThread().getName()
- self._catchers[name] = catcher
-
- def deregister(self, name=None,
- currentThread=threading.currentThread):
- if name is None:
- name = currentThread().getName()
- assert self._catchers.has_key(name), (
- "There is no PrintCatcher catcher for the thread %r" % name)
- del self._catchers[name]
-
-_printcatcher = None
-_oldstdout = None
-
-def install(**kw):
- global _printcatcher, _oldstdout, register, deregister
- if (not _printcatcher or sys.stdout is not _printcatcher):
- _oldstdout = sys.stdout
- _printcatcher = sys.stdout = PrintCatcher(**kw)
- register = _printcatcher.register
- deregister = _printcatcher.deregister
-
-def uninstall():
- global _printcatcher, _oldstdout, register, deregister
- if _printcatcher:
- sys.stdout = _oldstdout
- _printcatcher = _oldstdout = None
- register = not_installed_error
- deregister = not_installed_error
-
-def not_installed_error(*args, **kw):
- assert False, (
- "threadedprint has not yet been installed (call "
- "threadedprint.install())")
-
-register = deregister = not_installed_error
-
-class StdinCatcher(filemixin.FileMixin):
-
- def __init__(self, default=None, factory=None, paramwriter=None):
- assert len(filter(lambda x: x is not None,
- [default, factory, paramwriter])) <= 1, (
- "You can only provide one of default, factory, or paramwriter")
- if default:
- self._defaultfunc = self._readdefault
- elif factory:
- self._defaultfunc = self._readfactory
- elif paramwriter:
- self._defaultfunc = self._readparam
- else:
- self._defaultfunc = self._readerror
- self._default = default
- self._factory = factory
- self._paramwriter = paramwriter
- self._catchers = {}
-
- def read(self, size=None, currentThread=threading.currentThread):
- name = currentThread().getName()
- catchers = self._catchers
- if not catchers.has_key(name):
- return self._defaultfunc(name, size)
- else:
- catcher = catchers[name]
- return catcher.read(size)
-
- def _readdefault(self, name, size):
- self._default.read(size)
-
- def _readfactory(self, name, size):
- self._factory(name).read(size)
-
- def _readparam(self, name, size):
- self._paramreader(name, size)
-
- def _readerror(self, name, size):
- assert False, (
- "There is no StdinCatcher output stream for the thread %r"
- % name)
-
- def register(self, catcher, name=None,
- currentThread=threading.currentThread):
- if name is None:
- name = currentThread().getName()
- self._catchers[name] = catcher
-
- def deregister(self, catcher, name=None,
- currentThread=threading.currentThread):
- if name is None:
- name = currentThread().getName()
- assert self._catchers.has_key(name), (
- "There is no StdinCatcher catcher for the thread %r" % name)
- del self._catchers[name]
-
-_stdincatcher = None
-_oldstdin = None
-
-def install_stdin(**kw):
- global _stdincatcher, _oldstdin, register_stdin, deregister_stdin
- if not _stdincatcher:
- _oldstdin = sys.stdin
- _stdincatcher = sys.stdin = StdinCatcher(**kw)
- register_stdin = _stdincatcher.register
- deregister_stdin = _stdincatcher.deregister
-
-def uninstall():
- global _stdincatcher, _oldstin, register_stdin, deregister_stdin
- if _stdincatcher:
- sys.stdin = _oldstdin
- _stdincatcher = _oldstdin = None
- register_stdin = deregister_stdin = not_installed_error_stdin
-
-def not_installed_error_stdin(*args, **kw):
- assert False, (
- "threadedprint has not yet been installed for stdin (call "
- "threadedprint.install_stdin())")
diff --git a/lib/paste/util/threadinglocal.py b/lib/paste/util/threadinglocal.py
@@ -1,43 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Implementation of thread-local storage, for Python versions that don't
-have thread local storage natively.
-"""
-
-try:
- import threading
-except ImportError:
- # No threads, so "thread local" means process-global
- class local(object):
- pass
-else:
- try:
- local = threading.local
- except AttributeError:
- # Added in 2.4, but now we'll have to define it ourselves
- import thread
- class local(object):
-
- def __init__(self):
- self.__dict__['__objs'] = {}
-
- def __getattr__(self, attr, g=thread.get_ident):
- try:
- return self.__dict__['__objs'][g()][attr]
- except KeyError:
- raise AttributeError(
- "No variable %s defined for the thread %s"
- % (attr, g()))
-
- def __setattr__(self, attr, value, g=thread.get_ident):
- self.__dict__['__objs'].setdefault(g(), {})[attr] = value
-
- def __delattr__(self, attr, g=thread.get_ident):
- try:
- del self.__dict__['__objs'][g()][attr]
- except KeyError:
- raise AttributeError(
- "No variable %s defined for thread %s"
- % (attr, g()))
-
diff --git a/lib/paste/wsgilib.py b/lib/paste/wsgilib.py
@@ -1,597 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-"""
-A module of many disparate routines.
-"""
-
-# functions which moved to paste.request and paste.response
-# Deprecated around 15 Dec 2005
-from paste.request import get_cookies, parse_querystring, parse_formvars
-from paste.request import construct_url, path_info_split, path_info_pop
-from paste.response import HeaderDict, has_header, header_value, remove_header
-from paste.response import error_body_response, error_response, error_response_app
-
-from traceback import print_exception
-import urllib
-from cStringIO import StringIO
-import sys
-from urlparse import urlsplit
-import warnings
-
-__all__ = ['add_close', 'add_start_close', 'capture_output', 'catch_errors',
- 'catch_errors_app', 'chained_app_iters', 'construct_url',
- 'dump_environ', 'encode_unicode_app_iter', 'error_body_response',
- 'error_response', 'get_cookies', 'has_header', 'header_value',
- 'interactive', 'intercept_output', 'path_info_pop',
- 'path_info_split', 'raw_interactive', 'send_file']
-
-class add_close(object):
- """
- An an iterable that iterates over app_iter, then calls
- close_func.
- """
-
- def __init__(self, app_iterable, close_func):
- self.app_iterable = app_iterable
- self.app_iter = iter(app_iterable)
- self.close_func = close_func
- self._closed = False
-
- def __iter__(self):
- return self
-
- def next(self):
- return self.app_iter.next()
-
- def close(self):
- self._closed = True
- if hasattr(self.app_iterable, 'close'):
- self.app_iterable.close()
- self.close_func()
-
- def __del__(self):
- if not self._closed:
- # We can't raise an error or anything at this stage
- print >> sys.stderr, (
- "Error: app_iter.close() was not called when finishing "
- "WSGI request. finalization function %s not called"
- % self.close_func)
-
-class add_start_close(object):
- """
- An an iterable that iterates over app_iter, calls start_func
- before the first item is returned, then calls close_func at the
- end.
- """
-
- def __init__(self, app_iterable, start_func, close_func=None):
- self.app_iterable = app_iterable
- self.app_iter = iter(app_iterable)
- self.first = True
- self.start_func = start_func
- self.close_func = close_func
- self._closed = False
-
- def __iter__(self):
- return self
-
- def next(self):
- if self.first:
- self.start_func()
- self.first = False
- return self.app_iter.next()
-
- def close(self):
- self._closed = True
- if hasattr(self.app_iterable, 'close'):
- self.app_iterable.close()
- if self.close_func is not None:
- self.close_func()
-
- def __del__(self):
- if not self._closed:
- # We can't raise an error or anything at this stage
- print >> sys.stderr, (
- "Error: app_iter.close() was not called when finishing "
- "WSGI request. finalization function %s not called"
- % self.close_func)
-
-class chained_app_iters(object):
-
- """
- Chains several app_iters together, also delegating .close() to each
- of them.
- """
-
- def __init__(self, *chained):
- self.app_iters = chained
- self.chained = [iter(item) for item in chained]
- self._closed = False
-
- def __iter__(self):
- return self
-
- def next(self):
- if len(self.chained) == 1:
- return self.chained[0].next()
- else:
- try:
- return self.chained[0].next()
- except StopIteration:
- self.chained.pop(0)
- return self.next()
-
- def close(self):
- self._closed = True
- got_exc = None
- for app_iter in self.app_iters:
- try:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- except:
- got_exc = sys.exc_info()
- if got_exc:
- raise got_exc[0], got_exc[1], got_exc[2]
-
- def __del__(self):
- if not self._closed:
- # We can't raise an error or anything at this stage
- print >> sys.stderr, (
- "Error: app_iter.close() was not called when finishing "
- "WSGI request. finalization function %s not called"
- % self.close_func)
-
-class encode_unicode_app_iter(object):
- """
- Encodes an app_iterable's unicode responses as strings
- """
-
- def __init__(self, app_iterable, encoding=sys.getdefaultencoding(),
- errors='strict'):
- self.app_iterable = app_iterable
- self.app_iter = iter(app_iterable)
- self.encoding = encoding
- self.errors = errors
-
- def __iter__(self):
- return self
-
- def next(self):
- content = self.app_iter.next()
- if isinstance(content, unicode):
- content = content.encode(self.encoding, self.errors)
- return content
-
- def close(self):
- if hasattr(self.app_iterable, 'close'):
- self.app_iterable.close()
-
-def catch_errors(application, environ, start_response, error_callback,
- ok_callback=None):
- """
- Runs the application, and returns the application iterator (which should be
- passed upstream). If an error occurs then error_callback will be called with
- exc_info as its sole argument. If no errors occur and ok_callback is given,
- then it will be called with no arguments.
- """
- try:
- app_iter = application(environ, start_response)
- except:
- error_callback(sys.exc_info())
- raise
- if type(app_iter) in (list, tuple):
- # These won't produce exceptions
- if ok_callback:
- ok_callback()
- return app_iter
- else:
- return _wrap_app_iter(app_iter, error_callback, ok_callback)
-
-class _wrap_app_iter(object):
-
- def __init__(self, app_iterable, error_callback, ok_callback):
- self.app_iterable = app_iterable
- self.app_iter = iter(app_iterable)
- self.error_callback = error_callback
- self.ok_callback = ok_callback
- if hasattr(self.app_iterable, 'close'):
- self.close = self.app_iterable.close
-
- def __iter__(self):
- return self
-
- def next(self):
- try:
- return self.app_iter.next()
- except StopIteration:
- if self.ok_callback:
- self.ok_callback()
- raise
- except:
- self.error_callback(sys.exc_info())
- raise
-
-def catch_errors_app(application, environ, start_response, error_callback_app,
- ok_callback=None, catch=Exception):
- """
- Like ``catch_errors``, except error_callback_app should be a
- callable that will receive *three* arguments -- ``environ``,
- ``start_response``, and ``exc_info``. It should call
- ``start_response`` (*with* the exc_info argument!) and return an
- iterator.
- """
- try:
- app_iter = application(environ, start_response)
- except catch:
- return error_callback_app(environ, start_response, sys.exc_info())
- if type(app_iter) in (list, tuple):
- # These won't produce exceptions
- if ok_callback is not None:
- ok_callback()
- return app_iter
- else:
- return _wrap_app_iter_app(
- environ, start_response, app_iter,
- error_callback_app, ok_callback, catch=catch)
-
-class _wrap_app_iter_app(object):
-
- def __init__(self, environ, start_response, app_iterable,
- error_callback_app, ok_callback, catch=Exception):
- self.environ = environ
- self.start_response = start_response
- self.app_iterable = app_iterable
- self.app_iter = iter(app_iterable)
- self.error_callback_app = error_callback_app
- self.ok_callback = ok_callback
- self.catch = catch
- if hasattr(self.app_iterable, 'close'):
- self.close = self.app_iterable.close
-
- def __iter__(self):
- return self
-
- def next(self):
- try:
- return self.app_iter.next()
- except StopIteration:
- if self.ok_callback:
- self.ok_callback()
- raise
- except self.catch:
- if hasattr(self.app_iterable, 'close'):
- try:
- self.app_iterable.close()
- except:
- # @@: Print to wsgi.errors?
- pass
- new_app_iterable = self.error_callback_app(
- self.environ, self.start_response, sys.exc_info())
- app_iter = iter(new_app_iterable)
- if hasattr(new_app_iterable, 'close'):
- self.close = new_app_iterable.close
- self.next = app_iter.next
- return self.next()
-
-def raw_interactive(application, path='', raise_on_wsgi_error=False,
- **environ):
- """
- Runs the application in a fake environment.
- """
- assert "path_info" not in environ, "argument list changed"
- if raise_on_wsgi_error:
- errors = ErrorRaiser()
- else:
- errors = StringIO()
- 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_PROTOCOL': 'HTTP/1.0',
- # mandatory wsgi variables
- 'wsgi.version': (1, 0),
- 'wsgi.url_scheme': 'http',
- 'wsgi.input': StringIO(''),
- 'wsgi.errors': errors,
- 'wsgi.multithread': False,
- 'wsgi.multiprocess': False,
- 'wsgi.run_once': False,
- }
- if path:
- (_, _, path_info, query, fragment) = urlsplit(str(path))
- path_info = urllib.unquote(path_info)
- # urlsplit returns unicode so coerce it back to str
- path_info, query = str(path_info), str(query)
- basic_environ['PATH_INFO'] = path_info
- if query:
- basic_environ['QUERY_STRING'] = query
- for name, value in environ.items():
- name = name.replace('__', '.')
- basic_environ[name] = value
- if ('SERVER_NAME' in basic_environ
- 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)
- basic_environ['CONTENT_LENGTH'] = len(istream)
- data = {}
- output = []
- headers_set = []
- headers_sent = []
- def start_response(status, headers, exc_info=None):
- if exc_info:
- try:
- if headers_sent:
- # Re-raise original exception only if headers sent
- raise exc_info[0], exc_info[1], exc_info[2]
- finally:
- # avoid dangling circular reference
- exc_info = None
- elif headers_set:
- # You cannot set the headers more than once, unless the
- # exc_info is provided.
- raise AssertionError("Headers already set and no exc_info!")
- headers_set.append(True)
- data['status'] = status
- data['headers'] = headers
- return output.append
- app_iter = application(basic_environ, start_response)
- try:
- try:
- for s in app_iter:
- if not isinstance(s, str):
- raise ValueError(
- "The app_iter response can only contain str (not "
- "unicode); got: %r" % s)
- headers_sent.append(True)
- if not headers_set:
- raise AssertionError("Content sent w/o headers!")
- output.append(s)
- except TypeError, e:
- # Typically "iteration over non-sequence", so we want
- # to give better debugging information...
- e.args = ((e.args[0] + ' iterable: %r' % app_iter),) + e.args[1:]
- raise
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- return (data['status'], data['headers'], ''.join(output),
- errors.getvalue())
-
-class ErrorRaiser(object):
-
- def flush(self):
- pass
-
- def write(self, value):
- if not value:
- return
- raise AssertionError(
- "No errors should be written (got: %r)" % value)
-
- def writelines(self, seq):
- raise AssertionError(
- "No errors should be written (got lines: %s)" % list(seq))
-
- def getvalue(self):
- return ''
-
-def interactive(*args, **kw):
- """
- Runs the application interatively, wrapping `raw_interactive` but
- returning the output in a formatted way.
- """
- status, headers, content, errors = raw_interactive(*args, **kw)
- full = StringIO()
- if errors:
- full.write('Errors:\n')
- full.write(errors.strip())
- full.write('\n----------end errors\n')
- full.write(status + '\n')
- for name, value in headers:
- full.write('%s: %s\n' % (name, value))
- full.write('\n')
- full.write(content)
- return full.getvalue()
-interactive.proxy = 'raw_interactive'
-
-def dump_environ(environ, start_response):
- """
- Application which simply dumps the current environment
- variables out as a plain text response.
- """
- output = []
- keys = environ.keys()
- keys.sort()
- for k in keys:
- v = str(environ[k]).replace("\n","\n ")
- output.append("%s: %s\n" % (k, v))
- output.append("\n")
- content_length = environ.get("CONTENT_LENGTH", '')
- if content_length:
- output.append(environ['wsgi.input'].read(int(content_length)))
- output.append("\n")
- output = "".join(output)
- headers = [('Content-Type', 'text/plain'),
- ('Content-Length', str(len(output)))]
- start_response("200 OK", headers)
- return [output]
-
-def send_file(filename):
- warnings.warn(
- "wsgilib.send_file has been moved to paste.fileapp.FileApp",
- DeprecationWarning, 2)
- from paste import fileapp
- return fileapp.FileApp(filename)
-
-def capture_output(environ, start_response, application):
- """
- Runs application with environ and start_response, and captures
- status, headers, and body.
-
- Sends status and header, but *not* body. Returns (status,
- headers, body). Typically this is used like:
-
- .. code-block:: python
-
- def dehtmlifying_middleware(application):
- def replacement_app(environ, start_response):
- status, headers, body = capture_output(
- environ, start_response, application)
- content_type = header_value(headers, 'content-type')
- if (not content_type
- or not content_type.startswith('text/html')):
- return [body]
- body = re.sub(r'<.*?>', '', body)
- return [body]
- return replacement_app
-
- """
- warnings.warn(
- 'wsgilib.capture_output has been deprecated in favor '
- 'of wsgilib.intercept_output',
- DeprecationWarning, 2)
- data = []
- output = StringIO()
- def replacement_start_response(status, headers, exc_info=None):
- if data:
- data[:] = []
- data.append(status)
- data.append(headers)
- start_response(status, headers, exc_info)
- return output.write
- app_iter = application(environ, replacement_start_response)
- try:
- for item in app_iter:
- output.write(item)
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- if not data:
- data.append(None)
- if len(data) < 2:
- data.append(None)
- data.append(output.getvalue())
- return data
-
-def intercept_output(environ, application, conditional=None,
- start_response=None):
- """
- Runs application with environ and captures status, headers, and
- body. None are sent on; you must send them on yourself (unlike
- ``capture_output``)
-
- Typically this is used like:
-
- .. code-block:: python
-
- def dehtmlifying_middleware(application):
- def replacement_app(environ, start_response):
- status, headers, body = intercept_output(
- environ, application)
- start_response(status, headers)
- content_type = header_value(headers, 'content-type')
- if (not content_type
- or not content_type.startswith('text/html')):
- return [body]
- body = re.sub(r'<.*?>', '', body)
- return [body]
- return replacement_app
-
- A third optional argument ``conditional`` should be a function
- that takes ``conditional(status, headers)`` and returns False if
- the request should not be intercepted. In that case
- ``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):
- def replacement_app(environ, start_response):
- status, headers, body = intercept_output(
- environ, application,
- lambda s, h: header_value(headers, 'content-type').startswith('text/html'),
- start_response)
- if status is None:
- return body
- start_response(status, headers)
- body = re.sub(r'<.*?>', '', body)
- return [body]
- return replacement_app
- """
- if conditional is not None and start_response is None:
- raise TypeError(
- "If you provide conditional you must also provide "
- "start_response")
- data = []
- output = StringIO()
- def replacement_start_response(status, headers, exc_info=None):
- if conditional is not None and not conditional(status, headers):
- data.append(None)
- return start_response(status, headers, exc_info)
- if data:
- data[:] = []
- data.append(status)
- data.append(headers)
- return output.write
- app_iter = application(environ, replacement_start_response)
- if data[0] is None:
- return (None, None, app_iter)
- try:
- for item in app_iter:
- output.write(item)
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- if not data:
- data.append(None)
- if len(data) < 2:
- data.append(None)
- data.append(output.getvalue())
- return data
-
-## Deprecation warning wrapper:
-
-class ResponseHeaderDict(HeaderDict):
-
- def __init__(self, *args, **kw):
- warnings.warn(
- "The class wsgilib.ResponseHeaderDict has been moved "
- "to paste.response.HeaderDict",
- DeprecationWarning, 2)
- HeaderDict.__init__(self, *args, **kw)
-
-def _warn_deprecated(new_func):
- new_name = new_func.func_name
- new_path = new_func.func_globals['__name__'] + '.' + new_name
- def replacement(*args, **kw):
- warnings.warn(
- "The function wsgilib.%s has been moved to %s"
- % (new_name, new_path),
- DeprecationWarning, 2)
- return new_func(*args, **kw)
- try:
- replacement.func_name = new_func.func_name
- except:
- pass
- return replacement
-
-# Put warnings wrapper in place for all public functions that
-# were imported from elsewhere:
-
-for _name in __all__:
- _func = globals()[_name]
- if (hasattr(_func, 'func_globals')
- and _func.func_globals['__name__'] != __name__):
- globals()[_name] = _warn_deprecated(_func)
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
-
diff --git a/lib/paste/wsgiwrappers.py b/lib/paste/wsgiwrappers.py
@@ -1,581 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""WSGI Wrappers for a Request and Response
-
-The WSGIRequest and WSGIResponse objects are light wrappers to make it easier
-to deal with an incoming request and sending a response.
-"""
-import re
-import warnings
-from pprint import pformat
-from Cookie import SimpleCookie
-from paste.request import EnvironHeaders, get_cookie_dict, \
- parse_dict_querystring, parse_formvars
-from paste.util.multidict import MultiDict, UnicodeMultiDict
-from paste.registry import StackedObjectProxy
-from paste.response import HeaderDict
-from paste.wsgilib import encode_unicode_app_iter
-from paste.httpheaders import ACCEPT_LANGUAGE
-from paste.util.mimeparse import desired_matches
-
-__all__ = ['WSGIRequest', 'WSGIResponse']
-
-_CHARSET_RE = re.compile(r';\s*charset=([^;]*)', re.I)
-
-class DeprecatedSettings(StackedObjectProxy):
- def _push_object(self, obj):
- warnings.warn('paste.wsgiwrappers.settings is deprecated: Please use '
- 'paste.wsgiwrappers.WSGIRequest.defaults instead',
- DeprecationWarning, 3)
- WSGIResponse.defaults._push_object(obj)
- StackedObjectProxy._push_object(self, obj)
-
-# settings is deprecated: use WSGIResponse.defaults instead
-settings = DeprecatedSettings(default=dict())
-
-class environ_getter(object):
- """For delegating an attribute to a key in self.environ."""
- # @@: Also __set__? Should setting be allowed?
- def __init__(self, key, default='', default_factory=None):
- self.key = key
- self.default = default
- self.default_factory = default_factory
- def __get__(self, obj, type=None):
- if type is None:
- return self
- if self.key not in obj.environ:
- if self.default_factory:
- val = obj.environ[self.key] = self.default_factory()
- return val
- else:
- return self.default
- return obj.environ[self.key]
-
- def __repr__(self):
- return '<Proxy for WSGI environ %r key>' % self.key
-
-class WSGIRequest(object):
- """WSGI Request API Object
-
- This object represents a WSGI request with a more friendly interface.
- This does not expose every detail of the WSGI environment, and attempts
- to express nothing beyond what is available in the environment
- dictionary.
-
- The only state maintained in this object is the desired ``charset``,
- its associated ``errors`` handler, and the ``decode_param_names``
- option.
-
- The incoming parameter values will be automatically coerced to unicode
- objects of the ``charset`` encoding when ``charset`` is set. The
- incoming parameter names are not decoded to unicode unless the
- ``decode_param_names`` option is enabled.
-
- When unicode is expected, ``charset`` will overridden by the the
- value of the ``Content-Type`` header's charset parameter if one was
- specified by the client.
-
- 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,
- 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
- 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.
-
- *All* other state is kept in the environment dictionary; this is
- essential for interoperability.
-
- You are free to subclass this object.
-
- """
- defaults = StackedObjectProxy(default=dict(charset=None, errors='replace',
- decode_param_names=False,
- language='en-us'))
- def __init__(self, environ):
- 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:
- # There's a charset: params will be coerced to unicode. In that
- # case, attempt to use the charset specified by the browser
- browser_charset = self.determine_browser_charset()
- if browser_charset:
- self.charset = browser_charset
- 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')
- script_name = environ_getter('SCRIPT_NAME')
- path_info = environ_getter('PATH_INFO')
-
- def urlvars(self):
- """
- Return any variables matched in the URL (e.g.,
- ``wsgiorg.routing_args``).
- """
- if 'paste.urlvars' in self.environ:
- return self.environ['paste.urlvars']
- elif 'wsgiorg.routing_args' in self.environ:
- return self.environ['wsgiorg.routing_args'][1]
- 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'))
- host = property(host, doc=host.__doc__)
-
- def languages(self):
- """Return a list of preferred languages, most preferred first.
-
- The list may be empty.
- """
- if self._languages is not None:
- return self._languages
- acceptLanguage = self.environ.get('HTTP_ACCEPT_LANGUAGE')
- langs = ACCEPT_LANGUAGE.parse(self.environ)
- fallback = self.defaults.get('language', 'en-us')
- if not fallback:
- return langs
- if fallback not in langs:
- langs.append(fallback)
- index = langs.index(fallback)
- langs[index+1:] = []
- self._languages = langs
- return self._languages
- languages = property(languages, doc=languages.__doc__)
-
- def _GET(self):
- return parse_dict_querystring(self.environ)
-
- def GET(self):
- """
- Dictionary-like object representing the QUERY_STRING
- parameters. Always present, if possibly empty.
-
- If the same key is present in the query string multiple times, a
- list of its values can be retrieved from the ``MultiDict`` via
- the ``getall`` method.
-
- Returns a ``MultiDict`` container or a ``UnicodeMultiDict`` when
- ``charset`` is set.
- """
- params = self._GET()
- if self.charset:
- params = UnicodeMultiDict(params, encoding=self.charset,
- errors=self.errors,
- decode_keys=self.decode_param_names)
- return params
- GET = property(GET, doc=GET.__doc__)
-
- def _POST(self):
- return parse_formvars(self.environ, include_get_vars=False)
-
- def POST(self):
- """Dictionary-like object representing the POST body.
-
- Most values are encoded strings, or unicode strings when
- ``charset`` is set. There may also be FieldStorage objects
- representing file uploads. If this is not a POST request, or the
- body is not encoded fields (e.g., an XMLRPC request) then this
- will be empty.
-
- This will consume wsgi.input when first accessed if applicable,
- but the raw version will be put in
- environ['paste.parsed_formvars'].
-
- Returns a ``MultiDict`` container or a ``UnicodeMultiDict`` when
- ``charset`` is set.
- """
- params = self._POST()
- if self.charset:
- params = UnicodeMultiDict(params, encoding=self.charset,
- errors=self.errors,
- decode_keys=self.decode_param_names)
- return params
- POST = property(POST, doc=POST.__doc__)
-
- def params(self):
- """Dictionary-like object of keys from POST, GET, URL dicts
-
- Return a key value from the parameters, they are checked in the
- following order: POST, GET, URL
-
- Additional methods supported:
-
- ``getlist(key)``
- Returns a list of all the values by that key, collected from
- POST, GET, URL dicts
-
- Returns a ``MultiDict`` container or a ``UnicodeMultiDict`` when
- ``charset`` is set.
- """
- params = MultiDict()
- params.update(self._POST())
- params.update(self._GET())
- if self.charset:
- params = UnicodeMultiDict(params, encoding=self.charset,
- errors=self.errors,
- decode_keys=self.decode_param_names)
- return params
- params = property(params, doc=params.__doc__)
-
- def cookies(self):
- """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__)
-
- def determine_browser_charset(self):
- """
- Determine the encoding as specified by the browser via the
- Content-Type's charset parameter, if one is set
- """
- charset_match = _CHARSET_RE.search(self.headers.get('Content-Type', ''))
- if charset_match:
- return charset_match.group(1)
-
- 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,
- self.environ.get('HTTP_ACCEPT', '*/*'))
-
- def __repr__(self):
- """Show important attributes of the WSGIRequest"""
- pf = pformat
- msg = '<%s.%s object at 0x%x method=%s,' % \
- (self.__class__.__module__, self.__class__.__name__,
- id(self), pf(self.method))
- msg += '\nscheme=%s, host=%s, script_name=%s, path_info=%s,' % \
- (pf(self.scheme), pf(self.host), pf(self.script_name),
- pf(self.path_info))
- msg += '\nlanguages=%s,' % pf(self.languages)
- if self.charset:
- msg += ' charset=%s, errors=%s,' % (pf(self.charset),
- pf(self.errors))
- msg += '\nGET=%s,' % pf(self.GET)
- msg += '\nPOST=%s,' % pf(self.POST)
- msg += '\ncookies=%s>' % pf(self.cookies)
- return msg
-
-class WSGIResponse(object):
- """A basic HTTP response with content, headers, and out-bound cookies
-
- The class variable ``defaults`` specifies default values for
- ``content_type``, ``charset`` and ``errors``. These can be overridden
- for the current request via the registry.
-
- """
- defaults = StackedObjectProxy(
- default=dict(content_type='text/html', charset='utf-8',
- errors='strict', headers={'Cache-Control':'no-cache'})
- )
- def __init__(self, content='', mimetype=None, code=200):
- self._iter = None
- self._is_str_iter = True
-
- self.content = content
- self.headers = HeaderDict()
- self.cookies = SimpleCookie()
- self.status_code = code
-
- defaults = self.defaults._current_obj()
- if not mimetype:
- mimetype = defaults.get('content_type', 'text/html')
- charset = defaults.get('charset')
- if charset:
- mimetype = '%s; charset=%s' % (mimetype, charset)
- self.headers.update(defaults.get('headers', {}))
- self.headers['Content-Type'] = mimetype
- self.errors = defaults.get('errors', 'strict')
-
- def __str__(self):
- """Returns a rendition of the full HTTP message, including headers.
-
- When the content is an iterator, the actual content is replaced with the
- output of str(iterator) (to avoid exhausting the iterator).
- """
- if self._is_str_iter:
- content = ''.join(self.get_content())
- else:
- content = str(self.content)
- 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):
- response = WSGIResponse()
- 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)
- response_headers = self.headers.headeritems()
- for c in self.cookies.values():
- response_headers.append(('Set-Cookie', c.output(header='')))
- start_response(status, response_headers)
- is_file = isinstance(self.content, file)
- if 'wsgi.file_wrapper' in environ and is_file:
- return environ['wsgi.file_wrapper'](self.content)
- 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
- parameter, if one is set
- """
- 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
- """
- warnings.warn('WSGIResponse.has_header is deprecated, use '
- 'WSGIResponse.headers.has_key instead', DeprecationWarning,
- 2)
- return self.headers.has_key(header)
-
- def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
- domain=None, secure=None, httponly=None):
- """
- Define a cookie to be sent via the outgoing HTTP headers
- """
- self.cookies[key] = value
- for var_name, var_value in [
- ('max_age', max_age), ('path', path), ('domain', domain),
- ('secure', secure), ('expires', expires), ('httponly', httponly)]:
- if var_value is not None and var_value is not False:
- self.cookies[key][var_name.replace('_', '-')] = var_value
-
- def delete_cookie(self, key, path='/', domain=None):
- """
- Notify the browser the specified cookie has expired and should be
- deleted (via the outgoing HTTP headers)
- """
- self.cookies[key] = ''
- if path is not None:
- self.cookies[key]['path'] = path
- if domain is not None:
- self.cookies[key]['domain'] = domain
- self.cookies[key]['expires'] = 0
- self.cookies[key]['max-age'] = 0
-
- def _set_content(self, content):
- if hasattr(content, '__iter__'):
- self._iter = content
- if isinstance(content, list):
- self._is_str_iter = True
- else:
- self._is_str_iter = False
- else:
- self._iter = [content]
- self._is_str_iter = True
- content = property(lambda self: self._iter, _set_content,
- doc='Get/set the specified content, where content can '
- 'be: a string, a list of strings, a generator function '
- 'that yields strings, or an iterable object that '
- 'produces strings.')
-
- def get_content(self):
- """
- Returns the content as an iterable of strings, encoding each element of
- the iterator from a Unicode object if necessary.
- """
- charset = self.determine_charset()
- if charset:
- 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:
- (status, headers, iterable)
- """
- status_text = STATUS_CODE_TEXT[self.status_code]
- status = '%s %s' % (self.status_code, status_text)
- response_headers = self.headers.headeritems()
- 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 " \
- 'is an iterator)' % self.__class__.__name__
- self.content.append(content)
-
- def flush(self):
- pass
-
- def tell(self):
- if not self._is_str_iter:
- raise IOError, 'This %s instance cannot tell its position: (content ' \
- 'is an iterator)' % self.__class__.__name__
- return sum([len(chunk) for chunk in self._iter])
-
- ########################################
- ## Content-type and charset
-
- def charset__get(self):
- """
- Get/set the charset (in the Content-Type)
- """
- header = self.headers.get('content-type')
- if not header:
- return None
- match = _CHARSET_RE.search(header)
- if match:
- return match.group(1)
- return None
-
- def charset__set(self, charset):
- if charset is None:
- del self.charset
- return
- try:
- header = self.headers.pop('content-type')
- except KeyError:
- raise AttributeError(
- "You cannot set the charset when no content-type is defined")
- match = _CHARSET_RE.search(header)
- if match:
- header = header[:match.start()] + header[match.end():]
- header += '; charset=%s' % charset
- self.headers['content-type'] = header
-
- def charset__del(self):
- try:
- header = self.headers.pop('content-type')
- except KeyError:
- # Don't need to remove anything
- return
- match = _CHARSET_RE.search(header)
- if match:
- header = header[:match.start()] + header[match.end():]
- self.headers['content-type'] = header
-
- charset = property(charset__get, charset__set, charset__del, doc=charset__get.__doc__)
-
- def content_type__get(self):
- """
- Get/set the Content-Type header (or None), *without* the
- charset or any parameters.
-
- If you include parameters (or ``;`` at all) when setting the
- content_type, any existing parameters will be deleted;
- otherwise they will be preserved.
- """
- header = self.headers.get('content-type')
- if not header:
- return None
- return header.split(';', 1)[0]
-
- def content_type__set(self, value):
- if ';' not in value:
- header = self.headers.get('content-type', '')
- if ';' in header:
- params = header.split(';', 1)[1]
- value += ';' + params
- self.headers['content-type'] = value
-
- def content_type__del(self):
- try:
- del self.headers['content-type']
- except KeyError:
- pass
-
- content_type = property(content_type__get, content_type__set,
- content_type__del, doc=content_type__get.__doc__)
-
-## @@ I'd love to remove this, but paste.httpexceptions.get_exception
-## doesn't seem to work...
-# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
-STATUS_CODE_TEXT = {
- 100: 'CONTINUE',
- 101: 'SWITCHING PROTOCOLS',
- 200: 'OK',
- 201: 'CREATED',
- 202: 'ACCEPTED',
- 203: 'NON-AUTHORITATIVE INFORMATION',
- 204: 'NO CONTENT',
- 205: 'RESET CONTENT',
- 206: 'PARTIAL CONTENT',
- 226: 'IM USED',
- 300: 'MULTIPLE CHOICES',
- 301: 'MOVED PERMANENTLY',
- 302: 'FOUND',
- 303: 'SEE OTHER',
- 304: 'NOT MODIFIED',
- 305: 'USE PROXY',
- 306: 'RESERVED',
- 307: 'TEMPORARY REDIRECT',
- 400: 'BAD REQUEST',
- 401: 'UNAUTHORIZED',
- 402: 'PAYMENT REQUIRED',
- 403: 'FORBIDDEN',
- 404: 'NOT FOUND',
- 405: 'METHOD NOT ALLOWED',
- 406: 'NOT ACCEPTABLE',
- 407: 'PROXY AUTHENTICATION REQUIRED',
- 408: 'REQUEST TIMEOUT',
- 409: 'CONFLICT',
- 410: 'GONE',
- 411: 'LENGTH REQUIRED',
- 412: 'PRECONDITION FAILED',
- 413: 'REQUEST ENTITY TOO LARGE',
- 414: 'REQUEST-URI TOO LONG',
- 415: 'UNSUPPORTED MEDIA TYPE',
- 416: 'REQUESTED RANGE NOT SATISFIABLE',
- 417: 'EXPECTATION FAILED',
- 500: 'INTERNAL SERVER ERROR',
- 501: 'NOT IMPLEMENTED',
- 502: 'BAD GATEWAY',
- 503: 'SERVICE UNAVAILABLE',
- 504: 'GATEWAY TIMEOUT',
- 505: 'HTTP VERSION NOT SUPPORTED',
-}
diff --git a/lib/recaptcha/__init__.py b/lib/recaptcha/__init__.py
@@ -1 +0,0 @@
-__import__('pkg_resources').declare_namespace(__name__)
diff --git a/lib/recaptcha/client/__init__.py b/lib/recaptcha/client/__init__.py
diff --git a/lib/recaptcha/client/captcha.py b/lib/recaptcha/client/captcha.py
@@ -1,92 +0,0 @@
-import urllib2, urllib
-
-API_SSL_SERVER="https://api-secure.recaptcha.net"
-API_SERVER="http://api.recaptcha.net"
-VERIFY_SERVER="api-verify.recaptcha.net"
-
-class RecaptchaResponse(object):
- def __init__(self, is_valid, error_code=None):
- self.is_valid = is_valid
- self.error_code = error_code
-
-def displayhtml (public_key,
- use_ssl = False,
- error = None):
- """Gets the HTML to display for reCAPTCHA
-
- public_key -- The public api key
- use_ssl -- Should the request be sent over ssl?
- error -- An error message to display (from RecaptchaResponse.error_code)"""
-
- error_param = ''
- if error:
- error_param = '&error=%s' % error
-
- if use_ssl:
- server = API_SSL_SERVER
- else:
- server = API_SERVER
-
- return """<script type="text/javascript" src="%(ApiServer)s/challenge?k=%(PublicKey)s%(ErrorParam)s"></script>
-
-<noscript>
- <iframe src="%(ApiServer)s/noscript?k=%(PublicKey)s%(ErrorParam)s" height="300" width="500" frameborder="0"></iframe><br />
- <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
- <input type='hidden' name='recaptcha_response_field' value='manual_challenge' />
-</noscript>
-""" % {
- 'ApiServer' : server,
- 'PublicKey' : public_key,
- 'ErrorParam' : error_param,
- }
-
-
-def submit (recaptcha_challenge_field,
- recaptcha_response_field,
- private_key,
- remoteip):
- """
- Submits a reCAPTCHA request for verification. Returns RecaptchaResponse
- for the request
-
- recaptcha_challenge_field -- The value of recaptcha_challenge_field from the form
- recaptcha_response_field -- The value of recaptcha_response_field from the form
- private_key -- your reCAPTCHA private key
- remoteip -- the user's ip address
- """
-
- if not (recaptcha_response_field and recaptcha_challenge_field and
- len (recaptcha_response_field) and len (recaptcha_challenge_field)):
- return RecaptchaResponse (is_valid = False, error_code = 'incorrect-captcha-sol')
-
-
-
- params = urllib.urlencode ({
- 'privatekey': private_key,
- 'remoteip' : remoteip,
- 'challenge': recaptcha_challenge_field,
- 'response' : recaptcha_response_field,
- })
-
- request = urllib2.Request (
- url = "http://%s/verify" % VERIFY_SERVER,
- data = params,
- headers = {
- "Content-type": "application/x-www-form-urlencoded",
- "User-agent": "reCAPTCHA Python"
- }
- )
-
- httpresp = urllib2.urlopen (request)
-
- return_values = httpresp.read ().splitlines ();
- httpresp.close();
-
- return_code = return_values [0]
-
- if (return_code == "true"):
- return RecaptchaResponse (is_valid=True)
- else:
- return RecaptchaResponse (is_valid=False, error_code = return_values [1])
-
-
diff --git a/lib/recaptcha/client/mailhide.py b/lib/recaptcha/client/mailhide.py
@@ -1,68 +0,0 @@
-import base64
-import cgi
-
-try:
- from Crypto.Cipher import AES
-except:
- raise Exception ("You need the pycrpyto library: http://cheeseshop.python.org/pypi/pycrypto/")
-
-MAIL_HIDE_BASE="http://mailhide.recaptcha.net"
-
-def asurl (email,
- public_key,
- private_key):
- """Wraps an email address with reCAPTCHA mailhide and
- returns the url. public_key is the public key from reCAPTCHA
- (in the base 64 encoded format). Private key is the AES key, and should
- be 32 hex chars."""
-
- cryptmail = _encrypt_string (email, base64.b16decode (private_key, casefold=True), '\0' * 16)
- base64crypt = base64.urlsafe_b64encode (cryptmail)
-
- return "%s/d?k=%s&c=%s" % (MAIL_HIDE_BASE, public_key, base64crypt)
-
-def ashtml (email,
- public_key,
- private_key):
- """Wraps an email address with reCAPTCHA Mailhide and
- returns html that displays the email"""
-
- url = asurl (email, public_key, private_key)
- (userpart, domainpart) = _doterizeemail (email)
-
- return """%(user)s<a href='%(url)s' onclick="window.open('%(url)s', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;" title="Reveal this e-mail address">...</a>@%(domain)s""" % {
- 'user' : cgi.escape (userpart),
- 'domain' : cgi.escape (domainpart),
- 'url' : cgi.escape (url),
- }
-
-
-def _pad_string (str, block_size):
- numpad = block_size - (len (str) % block_size)
- return str + numpad * chr (numpad)
-
-def _encrypt_string (str, aes_key, aes_iv):
- if len (aes_key) != 16:
- raise Exception ("expecting key of length 16")
- if len (aes_iv) != 16:
- raise Exception ("expecting iv of length 16")
- return AES.new (aes_key, AES.MODE_CBC, aes_iv).encrypt (_pad_string (str, 16))
-
-def _doterizeemail (email):
- """replaces part of the username with dots"""
-
- try:
- [user, domain] = email.split ('@')
- except:
- # handle invalid emails... sorta
- user = email
- domain = ""
-
- if len(user) <= 4:
- user_prefix = user[:1]
- elif len(user) <= 6:
- user_prefix = user[:3]
- else:
- user_prefix = user[:4]
-
- return (user_prefix, domain)
diff --git a/lib/smartypants.py b/lib/smartypants.py
@@ -1,903 +0,0 @@
-#!/usr/bin/python
-
-r"""
-==============
-smartypants.py
-==============
-
-----------------------------
-SmartyPants ported to Python
-----------------------------
-
-Ported by `Chad Miller`_
-Copyright (c) 2004, 2007 Chad Miller
-
-original `SmartyPants`_ by `John Gruber`_
-Copyright (c) 2003 John Gruber
-
-
-Synopsis
-========
-
-A smart-quotes plugin for Pyblosxom_.
-
-The priginal "SmartyPants" is a free web publishing plug-in for Movable Type,
-Blosxom, and BBEdit that easily translates plain ASCII punctuation characters
-into "smart" typographic punctuation HTML entities.
-
-This software, *smartypants.py*, endeavours to be a functional port of
-SmartyPants to Python, for use with Pyblosxom_.
-
-
-Description
-===========
-
-SmartyPants can perform the following transformations:
-
-- Straight quotes ( " and ' ) into "curly" quote HTML entities
-- Backticks-style quotes (\`\`like this'') into "curly" quote HTML entities
-- Dashes (``--`` and ``---``) into en- and em-dash entities
-- Three consecutive dots (``...`` or ``. . .``) into an ellipsis entity
-
-This means you can write, edit, and save your posts using plain old
-ASCII straight quotes, plain dashes, and plain dots, but your published
-posts (and final HTML output) will appear with smart quotes, em-dashes,
-and proper ellipses.
-
-SmartyPants does not modify characters within ``<pre>``, ``<code>``, ``<kbd>``,
-``<math>`` or ``<script>`` tag blocks. Typically, these tags are used to
-display text where smart quotes and other "smart punctuation" would not be
-appropriate, such as source code or example markup.
-
-
-Backslash Escapes
-=================
-
-If you need to use literal straight quotes (or plain hyphens and
-periods), SmartyPants accepts the following backslash escape sequences
-to force non-smart punctuation. It does so by transforming the escape
-sequence into a decimal-encoded HTML entity:
-
-(FIXME: table here.)
-
-.. comment It sucks that there's a disconnect between the visual layout and table markup when special characters are involved.
-.. comment ====== ===== =========
-.. comment Escape Value Character
-.. comment ====== ===== =========
-.. comment \\\\\\\\ \ \\\\
-.. comment \\\\" " "
-.. comment \\\\' ' '
-.. comment \\\\. . .
-.. comment \\\\- - \-
-.. comment \\\\` ` \`
-.. comment ====== ===== =========
-
-This is useful, for example, when you want to use straight quotes as
-foot and inch marks: 6'2" tall; a 17" iMac.
-
-Options
-=======
-
-For Pyblosxom users, the ``smartypants_attributes`` attribute is where you
-specify configuration options.
-
-Numeric values are the easiest way to configure SmartyPants' behavior:
-
-"0"
- Suppress all transformations. (Do nothing.)
-"1"
- Performs default SmartyPants transformations: quotes (including
- \`\`backticks'' -style), em-dashes, and ellipses. "``--``" (dash dash)
- is used to signify an em-dash; there is no support for en-dashes.
-
-"2"
- Same as smarty_pants="1", except that it uses the old-school typewriter
- shorthand for dashes: "``--``" (dash dash) for en-dashes, "``---``"
- (dash dash dash)
- for em-dashes.
-
-"3"
- Same as smarty_pants="2", but inverts the shorthand for dashes:
- "``--``" (dash dash) for em-dashes, and "``---``" (dash dash dash) for
- en-dashes.
-
-"-1"
- Stupefy mode. Reverses the SmartyPants transformation process, turning
- the HTML entities produced by SmartyPants into their ASCII equivalents.
- E.g. "“" is turned into a simple double-quote ("), "—" is
- turned into two dashes, etc.
-
-
-The following single-character attribute values can be combined to toggle
-individual transformations from within the smarty_pants attribute. For
-example, to educate normal quotes and em-dashes, but not ellipses or
-\`\`backticks'' -style quotes:
-
-``py['smartypants_attributes'] = "1"``
-
-"q"
- Educates normal quote characters: (") and (').
-
-"b"
- Educates \`\`backticks'' -style double quotes.
-
-"B"
- Educates \`\`backticks'' -style double quotes and \`single' quotes.
-
-"d"
- Educates em-dashes.
-
-"D"
- Educates em-dashes and en-dashes, using old-school typewriter shorthand:
- (dash dash) for en-dashes, (dash dash dash) for em-dashes.
-
-"i"
- Educates em-dashes and en-dashes, using inverted old-school typewriter
- shorthand: (dash dash) for em-dashes, (dash dash dash) for en-dashes.
-
-"e"
- Educates ellipses.
-
-"w"
- Translates any instance of ``"`` into a normal double-quote character.
- This should be of no interest to most people, but of particular interest
- to anyone who writes their posts using Dreamweaver, as Dreamweaver
- inexplicably uses this entity to represent a literal double-quote
- character. SmartyPants only educates normal quotes, not entities (because
- ordinarily, entities are used for the explicit purpose of representing the
- specific character they represent). The "w" option must be used in
- conjunction with one (or both) of the other quote options ("q" or "b").
- Thus, if you wish to apply all SmartyPants transformations (quotes, en-
- and em-dashes, and ellipses) and also translate ``"`` entities into
- regular quotes so SmartyPants can educate them, you should pass the
- following to the smarty_pants attribute:
-
-The ``smartypants_forbidden_flavours`` list contains pyblosxom flavours for
-which no Smarty Pants rendering will occur.
-
-
-Caveats
-=======
-
-Why You Might Not Want to Use Smart Quotes in Your Weblog
----------------------------------------------------------
-
-For one thing, you might not care.
-
-Most normal, mentally stable individuals do not take notice of proper
-typographic punctuation. Many design and typography nerds, however, break
-out in a nasty rash when they encounter, say, a restaurant sign that uses
-a straight apostrophe to spell "Joe's".
-
-If you're the sort of person who just doesn't care, you might well want to
-continue not caring. Using straight quotes -- and sticking to the 7-bit
-ASCII character set in general -- is certainly a simpler way to live.
-
-Even if you I *do* care about accurate typography, you still might want to
-think twice before educating the quote characters in your weblog. One side
-effect of publishing curly quote HTML entities is that it makes your
-weblog a bit harder for others to quote from using copy-and-paste. What
-happens is that when someone copies text from your blog, the copied text
-contains the 8-bit curly quote characters (as well as the 8-bit characters
-for em-dashes and ellipses, if you use these options). These characters
-are not standard across different text encoding methods, which is why they
-need to be encoded as HTML entities.
-
-People copying text from your weblog, however, may not notice that you're
-using curly quotes, and they'll go ahead and paste the unencoded 8-bit
-characters copied from their browser into an email message or their own
-weblog. When pasted as raw "smart quotes", these characters are likely to
-get mangled beyond recognition.
-
-That said, my own opinion is that any decent text editor or email client
-makes it easy to stupefy smart quote characters into their 7-bit
-equivalents, and I don't consider it my problem if you're using an
-indecent text editor or email client.
-
-
-Algorithmic Shortcomings
-------------------------
-
-One situation in which quotes will get curled the wrong way is when
-apostrophes are used at the start of leading contractions. For example:
-
-``'Twas the night before Christmas.``
-
-In the case above, SmartyPants will turn the apostrophe into an opening
-single-quote, when in fact it should be a closing one. I don't think
-this problem can be solved in the general case -- every word processor
-I've tried gets this wrong as well. In such cases, it's best to use the
-proper HTML entity for closing single-quotes (``’``) by hand.
-
-
-Bugs
-====
-
-To file bug reports or feature requests (other than topics listed in the
-Caveats section above) please send email to: mailto:smartypantspy@chad.org
-
-If the bug involves quotes being curled the wrong way, please send example
-text to illustrate.
-
-To Do list
-----------
-
-- Provide a function for use within templates to quote anything at all.
-
-
-Version History
-===============
-
-1.5_1.6: Fri, 27 Jul 2007 07:06:40 -0400
- - Fixed bug where blocks of precious unalterable text was instead
- interpreted. Thanks to Le Roux and Dirk van Oosterbosch.
-
-1.5_1.5: Sat, 13 Aug 2005 15:50:24 -0400
- - Fix bogus magical quotation when there is no hint that the
- user wants it, e.g., in "21st century". Thanks to Nathan Hamblen.
- - Be smarter about quotes before terminating numbers in an en-dash'ed
- range.
-
-1.5_1.4: Thu, 10 Feb 2005 20:24:36 -0500
- - Fix a date-processing bug, as reported by jacob childress.
- - Begin a test-suite for ensuring correct output.
- - Removed import of "string", since I didn't really need it.
- (This was my first every Python program. Sue me!)
-
-1.5_1.3: Wed, 15 Sep 2004 18:25:58 -0400
- - Abort processing if the flavour is in forbidden-list. Default of
- [ "rss" ] (Idea of Wolfgang SCHNERRING.)
- - Remove stray virgules from en-dashes. Patch by Wolfgang SCHNERRING.
-
-1.5_1.2: Mon, 24 May 2004 08:14:54 -0400
- - Some single quotes weren't replaced properly. Diff-tesuji played
- by Benjamin GEIGER.
-
-1.5_1.1: Sun, 14 Mar 2004 14:38:28 -0500
- - Support upcoming pyblosxom 0.9 plugin verification feature.
-
-1.5_1.0: Tue, 09 Mar 2004 08:08:35 -0500
- - Initial release
-
-Version Information
--------------------
-
-Version numbers will track the SmartyPants_ version numbers, with the addition
-of an underscore and the smartypants.py version on the end.
-
-New versions will be available at `http://wiki.chad.org/SmartyPantsPy`_
-
-.. _http://wiki.chad.org/SmartyPantsPy: http://wiki.chad.org/SmartyPantsPy
-
-Authors
-=======
-
-`John Gruber`_ did all of the hard work of writing this software in Perl for
-`Movable Type`_ and almost all of this useful documentation. `Chad Miller`_
-ported it to Python to use with Pyblosxom_.
-
-
-Additional Credits
-==================
-
-Portions of the SmartyPants original work are based on Brad Choate's nifty
-MTRegex plug-in. `Brad Choate`_ also contributed a few bits of source code to
-this plug-in. Brad Choate is a fine hacker indeed.
-
-`Jeremy Hedley`_ and `Charles Wiltgen`_ deserve mention for exemplary beta
-testing of the original SmartyPants.
-
-`Rael Dornfest`_ ported SmartyPants to Blosxom.
-
-.. _Brad Choate: http://bradchoate.com/
-.. _Jeremy Hedley: http://antipixel.com/
-.. _Charles Wiltgen: http://playbacktime.com/
-.. _Rael Dornfest: http://raelity.org/
-
-
-Copyright and License
-=====================
-
-SmartyPants_ license::
-
- Copyright (c) 2003 John Gruber
- (http://daringfireball.net/)
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
-
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in
- the documentation and/or other materials provided with the
- distribution.
-
- * Neither the name "SmartyPants" nor the names of its contributors
- may be used to endorse or promote products derived from this
- software without specific prior written permission.
-
- This software is provided by the copyright holders and contributors "as
- is" and any express or implied warranties, including, but not limited
- to, the implied warranties of merchantability and fitness for a
- particular purpose are disclaimed. In no event shall the copyright
- owner or contributors be liable for any direct, indirect, incidental,
- special, exemplary, or consequential damages (including, but not
- limited to, procurement of substitute goods or services; loss of use,
- data, or profits; or business interruption) however caused and on any
- theory of liability, whether in contract, strict liability, or tort
- (including negligence or otherwise) arising in any way out of the use
- of this software, even if advised of the possibility of such damage.
-
-
-smartypants.py license::
-
- smartypants.py is a derivative work of SmartyPants.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
-
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in
- the documentation and/or other materials provided with the
- distribution.
-
- This software is provided by the copyright holders and contributors "as
- is" and any express or implied warranties, including, but not limited
- to, the implied warranties of merchantability and fitness for a
- particular purpose are disclaimed. In no event shall the copyright
- owner or contributors be liable for any direct, indirect, incidental,
- special, exemplary, or consequential damages (including, but not
- limited to, procurement of substitute goods or services; loss of use,
- data, or profits; or business interruption) however caused and on any
- theory of liability, whether in contract, strict liability, or tort
- (including negligence or otherwise) arising in any way out of the use
- of this software, even if advised of the possibility of such damage.
-
-
-
-.. _John Gruber: http://daringfireball.net/
-.. _Chad Miller: http://web.chad.org/
-
-.. _Pyblosxom: http://roughingit.subtlehints.net/pyblosxom
-.. _SmartyPants: http://daringfireball.net/projects/smartypants/
-.. _Movable Type: http://www.movabletype.org/
-
-"""
-
-default_smartypants_attr = "1"
-
-import re
-
-tags_to_skip_regex = re.compile(r"<(/)?(pre|code|kbd|script|math)[^>]*>", re.I)
-
-
-def verify_installation(request):
- return 1
- # assert the plugin is functional
-
-
-def cb_story(args):
- global default_smartypants_attr
-
- try:
- forbidden_flavours = args["entry"]["smartypants_forbidden_flavours"]
- except KeyError:
- forbidden_flavours = [ "rss" ]
-
- try:
- attributes = args["entry"]["smartypants_attributes"]
- except KeyError:
- attributes = default_smartypants_attr
-
- if attributes is None:
- attributes = default_smartypants_attr
-
- entryData = args["entry"].getData()
-
- try:
- if args["request"]["flavour"] in forbidden_flavours:
- return
- except KeyError:
- if "<" in args["entry"]["body"][0:15]: # sniff the stream
- return # abort if it looks like escaped HTML. FIXME
-
- # FIXME: make these configurable, perhaps?
- args["entry"]["body"] = smartyPants(entryData, attributes)
- args["entry"]["title"] = smartyPants(args["entry"]["title"], attributes)
-
-
-### interal functions below here
-
-def smartyPants(text, attr=default_smartypants_attr):
- convert_quot = False # should we translate " entities into normal quotes?
-
- # Parse attributes:
- # 0 : do nothing
- # 1 : set all
- # 2 : set all, using old school en- and em- dash shortcuts
- # 3 : set all, using inverted old school en and em- dash shortcuts
- #
- # q : quotes
- # b : backtick quotes (``double'' only)
- # B : backtick quotes (``double'' and `single')
- # d : dashes
- # D : old school dashes
- # i : inverted old school dashes
- # e : ellipses
- # w : convert " entities to " for Dreamweaver users
-
- skipped_tag_stack = []
- do_dashes = "0"
- do_backticks = "0"
- do_quotes = "0"
- do_ellipses = "0"
- do_stupefy = "0"
-
- if attr == "0":
- # Do nothing.
- return text
- elif attr == "1":
- do_quotes = "1"
- do_backticks = "1"
- do_dashes = "1"
- do_ellipses = "1"
- elif attr == "2":
- # Do everything, turn all options on, use old school dash shorthand.
- do_quotes = "1"
- do_backticks = "1"
- do_dashes = "2"
- do_ellipses = "1"
- elif attr == "3":
- # Do everything, turn all options on, use inverted old school dash shorthand.
- do_quotes = "1"
- do_backticks = "1"
- do_dashes = "3"
- do_ellipses = "1"
- elif attr == "-1":
- # Special "stupefy" mode.
- do_stupefy = "1"
- else:
- for c in attr:
- if c == "q": do_quotes = "1"
- elif c == "b": do_backticks = "1"
- elif c == "B": do_backticks = "2"
- elif c == "d": do_dashes = "1"
- elif c == "D": do_dashes = "2"
- elif c == "i": do_dashes = "3"
- elif c == "e": do_ellipses = "1"
- elif c == "w": convert_quot = "1"
- else:
- pass
- # ignore unknown option
-
- tokens = _tokenize(text)
- result = []
- in_pre = False
-
- prev_token_last_char = ""
- # This is a cheat, used to get some context
- # for one-character tokens that consist of
- # just a quote char. What we do is remember
- # the last character of the previous text
- # token, to use as context to curl single-
- # character quote tokens correctly.
-
- for cur_token in tokens:
- if cur_token[0] == "tag":
- # Don't mess with quotes inside some tags. This does not handle self <closing/> tags!
- result.append(cur_token[1])
- skip_match = tags_to_skip_regex.match(cur_token[1])
- if skip_match is not None:
- if not skip_match.group(1):
- skipped_tag_stack.append(skip_match.group(2).lower())
- in_pre = True
- else:
- if len(skipped_tag_stack) > 0:
- if skip_match.group(2).lower() == skipped_tag_stack[-1]:
- skipped_tag_stack.pop()
- else:
- pass
- # This close doesn't match the open. This isn't XHTML. We should barf here.
- if len(skipped_tag_stack) == 0:
- in_pre = False
- else:
- t = cur_token[1]
- last_char = t[-1:] # Remember last char of this token before processing.
- if not in_pre:
- oldstr = t
- t = processEscapes(t)
-
- if convert_quot != "0":
- t = re.sub('"', '"', t)
-
- if do_dashes != "0":
- if do_dashes == "1":
- t = educateDashes(t)
- if do_dashes == "2":
- t = educateDashesOldSchool(t)
- if do_dashes == "3":
- t = educateDashesOldSchoolInverted(t)
-
- if do_ellipses != "0":
- t = educateEllipses(t)
-
- # Note: backticks need to be processed before quotes.
- if do_backticks != "0":
- t = educateBackticks(t)
-
- if do_backticks == "2":
- t = educateSingleBackticks(t)
-
- if do_quotes != "0":
- if t == "'":
- # Special case: single-character ' token
- if re.match("\S", prev_token_last_char):
- t = "’"
- else:
- t = "‘"
- elif t == '"':
- # Special case: single-character " token
- if re.match("\S", prev_token_last_char):
- t = "”"
- else:
- t = "“"
-
- else:
- # Normal case:
- t = educateQuotes(t)
-
- if do_stupefy == "1":
- t = stupefyEntities(t)
-
- prev_token_last_char = last_char
- result.append(t)
-
- return "".join(result)
-
-
-def educateQuotes(str):
- """
- Parameter: String.
-
- Returns: The string, with "educated" curly quote HTML entities.
-
- Example input: "Isn't this fun?"
- Example output: “Isn’t this fun?”
- """
-
- oldstr = str
- punct_class = r"""[!"#\$\%'()*+,-.\/:;<=>?\@\[\\\]\^_`{|}~]"""
-
- # Special case if the very first character is a quote
- # followed by punctuation at a non-word-break. Close the quotes by brute force:
- str = re.sub(r"""^'(?=%s\\B)""" % (punct_class,), r"""’""", str)
- str = re.sub(r"""^"(?=%s\\B)""" % (punct_class,), r"""”""", str)
-
- # Special case for double sets of quotes, e.g.:
- # <p>He said, "'Quoted' words in a larger quote."</p>
- str = re.sub(r""""'(?=\w)""", """“‘""", str)
- str = re.sub(r"""'"(?=\w)""", """‘“""", str)
-
- # Special case for decade abbreviations (the '80s):
- str = re.sub(r"""\b'(?=\d{2}s)""", r"""’""", str)
-
- close_class = r"""[^\ \t\r\n\[\{\(\-]"""
- dec_dashes = r"""–|—"""
-
- # Get most opening single quotes:
- opening_single_quotes_regex = re.compile(r"""
- (
- \s | # a whitespace char, or
- | # a non-breaking space entity, or
- -- | # dashes, or
- &[mn]dash; | # named dash entities
- %s | # or decimal entities
- &\#x201[34]; # or hex
- )
- ' # the quote
- (?=\w) # followed by a word character
- """ % (dec_dashes,), re.VERBOSE)
- str = opening_single_quotes_regex.sub(r"""\1‘""", str)
-
- closing_single_quotes_regex = re.compile(r"""
- (%s)
- '
- (?!\s | s\b | \d)
- """ % (close_class,), re.VERBOSE)
- str = closing_single_quotes_regex.sub(r"""\1’""", str)
-
- closing_single_quotes_regex = re.compile(r"""
- (%s)
- '
- (\s | s\b)
- """ % (close_class,), re.VERBOSE)
- str = closing_single_quotes_regex.sub(r"""\1’\2""", str)
-
- # Any remaining single quotes should be opening ones:
- str = re.sub(r"""'""", r"""‘""", str)
-
- # Get most opening double quotes:
- opening_double_quotes_regex = re.compile(r"""
- (
- \s | # a whitespace char, or
- | # a non-breaking space entity, or
- -- | # dashes, or
- &[mn]dash; | # named dash entities
- %s | # or decimal entities
- &\#x201[34]; # or hex
- )
- " # the quote
- (?=\w) # followed by a word character
- """ % (dec_dashes,), re.VERBOSE)
- str = opening_double_quotes_regex.sub(r"""\1“""", str)
-
- # Double closing quotes:
- closing_double_quotes_regex = re.compile(r"""
- #(%s)? # character that indicates the quote should be closing
- "
- (?=\s)
- """ % (close_class,), re.VERBOSE)
- str = closing_double_quotes_regex.sub(r"""”""", str)
-
- closing_double_quotes_regex = re.compile(r"""
- (%s) # character that indicates the quote should be closing
- "
- """ % (close_class,), re.VERBOSE)
- str = closing_double_quotes_regex.sub(r"""\1”""", str)
-
- # Any remaining quotes should be opening ones.
- str = re.sub(r'"', r"""“""", str)
-
- return str
-
-
-def educateBackticks(str):
- """
- Parameter: String.
- Returns: The string, with ``backticks'' -style double quotes
- translated into HTML curly quote entities.
- Example input: ``Isn't this fun?''
- Example output: “Isn't this fun?”
- """
-
- str = re.sub(r"""``""", r"""“""", str)
- str = re.sub(r"""''""", r"""”""", str)
- return str
-
-
-def educateSingleBackticks(str):
- """
- Parameter: String.
- Returns: The string, with `backticks' -style single quotes
- translated into HTML curly quote entities.
-
- Example input: `Isn't this fun?'
- Example output: ‘Isn’t this fun?’
- """
-
- str = re.sub(r"""`""", r"""‘""", str)
- str = re.sub(r"""'""", r"""’""", str)
- return str
-
-
-def educateDashes(str):
- """
- Parameter: String.
-
- Returns: The string, with each instance of "--" translated to
- an em-dash HTML entity.
- """
-
- str = re.sub(r"""---""", r"""–""", str) # en (yes, backwards)
- str = re.sub(r"""--""", r"""—""", str) # em (yes, backwards)
- return str
-
-
-def educateDashesOldSchool(str):
- """
- Parameter: String.
-
- Returns: The string, with each instance of "--" translated to
- an en-dash HTML entity, and each "---" translated to
- an em-dash HTML entity.
- """
-
- str = re.sub(r"""---""", r"""—""", str) # em (yes, backwards)
- str = re.sub(r"""--""", r"""–""", str) # en (yes, backwards)
- return str
-
-
-def educateDashesOldSchoolInverted(str):
- """
- Parameter: String.
-
- Returns: The string, with each instance of "--" translated to
- an em-dash HTML entity, and each "---" translated to
- an en-dash HTML entity. Two reasons why: First, unlike the
- en- and em-dash syntax supported by
- EducateDashesOldSchool(), it's compatible with existing
- entries written before SmartyPants 1.1, back when "--" was
- only used for em-dashes. Second, em-dashes are more
- common than en-dashes, and so it sort of makes sense that
- the shortcut should be shorter to type. (Thanks to Aaron
- Swartz for the idea.)
- """
- str = re.sub(r"""---""", r"""–""", str) # em
- str = re.sub(r"""--""", r"""—""", str) # en
- return str
-
-
-
-def educateEllipses(str):
- """
- Parameter: String.
- Returns: The string, with each instance of "..." translated to
- an ellipsis HTML entity.
-
- Example input: Huh...?
- Example output: Huh…?
- """
-
- str = re.sub(r"""\.\.\.""", r"""…""", str)
- str = re.sub(r"""\. \. \.""", r"""…""", str)
- return str
-
-
-def stupefyEntities(str):
- """
- Parameter: String.
- Returns: The string, with each SmartyPants HTML entity translated to
- its ASCII counterpart.
-
- Example input: “Hello — world.”
- Example output: "Hello -- world."
- """
-
- str = re.sub(r"""–""", r"""-""", str) # en-dash
- str = re.sub(r"""—""", r"""--""", str) # em-dash
-
- str = re.sub(r"""‘""", r"""'""", str) # open single quote
- str = re.sub(r"""’""", r"""'""", str) # close single quote
-
- str = re.sub(r"""“""", r'''"''', str) # open double quote
- str = re.sub(r"""”""", r'''"''', str) # close double quote
-
- str = re.sub(r"""…""", r"""...""", str)# ellipsis
-
- return str
-
-
-def processEscapes(str):
- r"""
- Parameter: String.
- Returns: The string, with after processing the following backslash
- escape sequences. This is useful if you want to force a "dumb"
- quote or other character to appear.
-
- Escape Value
- ------ -----
- \\ \
- \" "
- \' '
- \. .
- \- -
- \` `
- """
- str = re.sub(r"""\\\\""", r"""\""", str)
- str = re.sub(r'''\\"''', r""""""", str)
- str = re.sub(r"""\\'""", r"""'""", str)
- str = re.sub(r"""\\\.""", r""".""", str)
- str = re.sub(r"""\\-""", r"""-""", str)
- str = re.sub(r"""\\`""", r"""`""", str)
-
- return str
-
-
-def _tokenize(str):
- """
- Parameter: String containing HTML markup.
- Returns: Reference to an array of the tokens comprising the input
- string. Each token is either a tag (possibly with nested,
- tags contained therein, such as <a href="<MTFoo>">, or a
- run of text between tags. Each element of the array is a
- two-element array; the first is either 'tag' or 'text';
- the second is the actual value.
-
- Based on the _tokenize() subroutine from Brad Choate's MTRegex plugin.
- <http://www.bradchoate.com/past/mtregex.php>
- """
-
- pos = 0
- length = len(str)
- tokens = []
-
- depth = 6
- nested_tags = "|".join(['(?:<(?:[^<>]',] * depth) + (')*>)' * depth)
- #match = r"""(?: <! ( -- .*? -- \s* )+ > ) | # comments
- # (?: <\? .*? \?> ) | # directives
- # %s # nested tags """ % (nested_tags,)
- tag_soup = re.compile(r"""([^<]*)(<[^>]*>)""")
-
- token_match = tag_soup.search(str)
-
- previous_end = 0
- while token_match is not None:
- if token_match.group(1):
- tokens.append(['text', token_match.group(1)])
-
- tokens.append(['tag', token_match.group(2)])
-
- previous_end = token_match.end()
- token_match = tag_soup.search(str, token_match.end())
-
- if previous_end < len(str):
- tokens.append(['text', str[previous_end:]])
-
- return tokens
-
-
-
-if __name__ == "__main__":
-
- import locale
-
- try:
- locale.setlocale(locale.LC_ALL, '')
- except:
- pass
-
- from docutils.core import publish_string
- docstring_html = publish_string(__doc__, writer_name='html')
-
- print docstring_html
-
-
- # Unit test output goes out stderr. No worries.
- import unittest
- sp = smartyPants
-
- class TestSmartypantsAllAttributes(unittest.TestCase):
- # the default attribute is "1", which means "all".
-
- def test_dates(self):
- self.assertEqual(sp("1440-80's"), "1440-80’s")
- self.assertEqual(sp("1440-'80s"), "1440-‘80s")
- self.assertEqual(sp("1440---'80s"), "1440–‘80s")
- self.assertEqual(sp("1960s"), "1960s") # no effect.
- self.assertEqual(sp("1960's"), "1960’s")
- self.assertEqual(sp("one two '60s"), "one two ‘60s")
- self.assertEqual(sp("'60s"), "‘60s")
-
- def test_skip_tags(self):
- self.assertEqual(
- sp("""<script type="text/javascript">\n<!--\nvar href = "http://www.google.com";\nvar linktext = "google";\ndocument.write('<a href="' + href + '">' + linktext + "</a>");\n//-->\n</script>"""),
- """<script type="text/javascript">\n<!--\nvar href = "http://www.google.com";\nvar linktext = "google";\ndocument.write('<a href="' + href + '">' + linktext + "</a>");\n//-->\n</script>""")
- self.assertEqual(
- sp("""<p>He said "Let's write some code." This code here <code>if True:\n\tprint "Okay"</code> is python code.</p>"""),
- """<p>He said “Let’s write some code.” This code here <code>if True:\n\tprint "Okay"</code> is python code.</p>""")
-
-
- def test_ordinal_numbers(self):
- self.assertEqual(sp("21st century"), "21st century") # no effect.
- self.assertEqual(sp("3rd"), "3rd") # no effect.
-
- def test_educated_quotes(self):
- self.assertEqual(sp('''"Isn't this fun?"'''), '''“Isn’t this fun?”''')
-
- unittest.main()
-
-
-
-
-__author__ = "Chad Miller <smartypantspy@chad.org>"
-__version__ = "1.5_1.6: Fri, 27 Jul 2007 07:06:40 -0400"
-__url__ = "http://wiki.chad.org/SmartyPantsPy"
-__description__ = "Smart-quotes, smart-ellipses, and smart-dashes for weblog entries in pyblosxom"
diff --git a/lib/webob/__init__.py b/lib/webob/__init__.py
@@ -1,2383 +0,0 @@
-from cStringIO import StringIO
-import sys
-import cgi
-import urllib
-import urlparse
-import re
-import textwrap
-from Cookie import BaseCookie
-from rfc822 import parsedate_tz, mktime_tz, formatdate
-from datetime import datetime, date, timedelta, tzinfo
-import time
-import calendar
-import tempfile
-import warnings
-from webob.datastruct import EnvironHeaders
-from webob.multidict import MultiDict, UnicodeMultiDict, NestedMultiDict, NoVars
-from webob.etag import AnyETag, NoETag, ETagMatcher, IfRange, NoIfRange
-from webob.headerdict import HeaderDict
-from webob.statusreasons import status_reasons
-from webob.cachecontrol import CacheControl, serialize_cache_control
-from webob.acceptparse import Accept, MIMEAccept, NilAccept, MIMENilAccept, NoAccept
-from webob.byterange import Range, ContentRange
-try:
- sorted
-except NameError:
- from webob.compat import sorted
-
-_CHARSET_RE = re.compile(r';\s*charset=([^;]*)', re.I)
-_SCHEME_RE = re.compile(r'^[a-z]+:', re.I)
-_PARAM_RE = re.compile(r'([a-z0-9]+)=(?:"([^"]*)"|([a-z0-9_.-]*))', re.I)
-_OK_PARAM_RE = re.compile(r'^[a-z0-9_.-]+$', re.I)
-
-__all__ = ['Request', 'Response', 'UTC', 'day', 'week', 'hour', 'minute', 'second', 'month', 'year', 'html_escape']
-
-class _UTC(tzinfo):
- def dst(self, dt):
- return timedelta(0)
- def utcoffset(self, dt):
- return timedelta(0)
- def tzname(self, dt):
- return 'UTC'
- def __repr__(self):
- return 'UTC'
-
-UTC = _UTC()
-
-def html_escape(s):
- """HTML-escape a string or object
-
- This converts any non-string objects passed into it to strings
- (actually, using ``unicode()``). All values returned are
- non-unicode strings (using ``&#num;`` entities for all non-ASCII
- characters).
-
- None is treated specially, and returns the empty string.
- """
- if s is None:
- return ''
- if not isinstance(s, basestring):
- if hasattr(s, '__unicode__'):
- s = unicode(s)
- else:
- s = str(s)
- s = cgi.escape(s, True)
- if isinstance(s, unicode):
- s = s.encode('ascii', 'xmlcharrefreplace')
- return s
-
-def timedelta_to_seconds(td):
- """
- Converts a timedelta instance to seconds.
- """
- return td.seconds + (td.days*24*60*60)
-
-day = timedelta(days=1)
-week = timedelta(weeks=1)
-hour = timedelta(hours=1)
-minute = timedelta(minutes=1)
-second = timedelta(seconds=1)
-# Estimate, I know; good enough for expirations
-month = timedelta(days=30)
-year = timedelta(days=365)
-
-class _NoDefault:
- def __repr__(self):
- return '(No Default)'
-NoDefault = _NoDefault()
-
-class environ_getter(object):
- """For delegating an attribute to a key in self.environ."""
-
- def __init__(self, key, default='', default_factory=None,
- settable=True, deletable=True, doc=None,
- rfc_section=None):
- self.key = key
- self.default = default
- self.default_factory = default_factory
- self.settable = settable
- self.deletable = deletable
- docstring = "Gets"
- if self.settable:
- docstring += " and sets"
- if self.deletable:
- docstring += " and deletes"
- docstring += " the %r key from the environment." % self.key
- docstring += _rfc_reference(self.key, rfc_section)
- if doc:
- docstring += '\n\n' + textwrap.dedent(doc)
- self.__doc__ = docstring
-
- def __get__(self, obj, type=None):
- if obj is None:
- return self
- if self.key not in obj.environ:
- if self.default_factory:
- val = obj.environ[self.key] = self.default_factory()
- return val
- else:
- return self.default
- return obj.environ[self.key]
-
- def __set__(self, obj, value):
- if not self.settable:
- raise AttributeError("Read-only attribute (key %r)" % self.key)
- if value is None:
- if self.key in obj.environ:
- del obj.environ[self.key]
- else:
- obj.environ[self.key] = value
-
- def __delete__(self, obj):
- if not self.deletable:
- raise AttributeError("You cannot delete the key %r" % self.key)
- del obj.environ[self.key]
-
- def __repr__(self):
- return '<Proxy for WSGI environ %r key>' % self.key
-
-class header_getter(object):
- """For delegating an attribute to a header in self.headers"""
-
- def __init__(self, header, default=None,
- settable=True, deletable=True, doc=None, rfc_section=None):
- self.header = header
- self.default = default
- self.settable = settable
- self.deletable = deletable
- docstring = "Gets"
- if self.settable:
- docstring += " and sets"
- if self.deletable:
- docstring += " and deletes"
- docstring += " they header %s from the headers" % self.header
- docstring += _rfc_reference(self.header, rfc_section)
- if doc:
- docstring += '\n\n' + textwrap.dedent(doc)
- self.__doc__ = docstring
-
- def __get__(self, obj, type=None):
- if obj is None:
- return self
- if self.header not in obj.headers:
- return self.default
- else:
- return obj.headers[self.header]
-
- def __set__(self, obj, value):
- if not self.settable:
- raise AttributeError("Read-only attribute (header %s)" % self.header)
- if value is None:
- if self.header in obj.headers:
- del obj.headers[self.header]
- else:
- if isinstance(value, unicode):
- # This is the standard encoding for headers:
- value = value.encode('ISO-8859-1')
- obj.headers[self.header] = value
-
- def __delete__(self, obj):
- if not self.deletable:
- raise AttributeError("You cannot delete the header %s" % self.header)
- del obj.headers[self.header]
-
- def __repr__(self):
- return '<Proxy for header %s>' % self.header
-
-class converter(object):
- """
- Wraps a decorator, and applies conversion for that decorator
- """
- def __init__(self, decorator, getter_converter, setter_converter, convert_name=None, doc=None, converter_args=()):
- self.decorator = decorator
- self.getter_converter = getter_converter
- self.setter_converter = setter_converter
- self.convert_name = convert_name
- self.converter_args = converter_args
- docstring = decorator.__doc__ or ''
- docstring += " Converts it as a "
- if convert_name:
- docstring += convert_name + '.'
- else:
- docstring += "%r and %r." % (getter_converter, setter_converter)
- if doc:
- docstring += '\n\n' + textwrap.dedent(doc)
- self.__doc__ = docstring
-
- def __get__(self, obj, type=None):
- if obj is None:
- return self
- value = self.decorator.__get__(obj, type)
- return self.getter_converter(value, *self.converter_args)
-
- def __set__(self, obj, value):
- value = self.setter_converter(value, *self.converter_args)
- self.decorator.__set__(obj, value)
-
- def __delete__(self, obj):
- self.decorator.__delete__(obj)
-
- def __repr__(self):
- if self.convert_name:
- name = ' %s' % self.convert_name
- else:
- name = ''
- return '<Converted %r%s>' % (self.decorator, name)
-
-def _rfc_reference(header, section):
- if not section:
- return ''
- major_section = section.split('.')[0]
- link = 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec%s.html#sec%s' % (
- major_section, section)
- if header.startswith('HTTP_'):
- header = header[5:].title().replace('_', '-')
- return " For more information on %s see `section %s <%s>`_." % (
- header, section, link)
-
-class deprecated_property(object):
- """
- Wraps a decorator, with a deprecation warning or error
- """
- def __init__(self, decorator, attr, message, warning=True):
- self.decorator = decorator
- self.attr = attr
- self.message = message
- self.warning = warning
-
- def __get__(self, obj, type=None):
- if obj is None:
- return self
- self.warn()
- return self.decorator.__get__(obj, type)
-
- def __set__(self, obj, value):
- self.warn()
- self.decorator.__set__(obj, value)
-
- def __delete__(self, obj):
- self.warn()
- self.decorator.__delete__(obj)
-
- def __repr__(self):
- return '<Deprecated attribute %s: %r>' % (
- self.attr,
- self.decorator)
-
- def warn(self):
- if not self.warning:
- raise DeprecationWarning(
- 'The attribute %s is deprecated: %s' % (self.attr, self.message))
- else:
- warnings.warn(
- 'The attribute %s is deprecated: %s' % (self.attr, self.message),
- DeprecationWarning,
- stacklevel=3)
-
-def _parse_date(value):
- if not value:
- return None
- t = parsedate_tz(value)
- if t is None:
- # Could not parse
- return None
- if t[-1] is None:
- # No timezone given. None would mean local time, but we'll force UTC
- t = t[:9] + (0,)
- t = mktime_tz(t)
- return datetime.fromtimestamp(t, UTC)
-
-def _serialize_date(dt):
- if dt is None:
- return None
- if isinstance(dt, unicode):
- dt = dt.encode('ascii')
- if isinstance(dt, str):
- return dt
- if isinstance(dt, timedelta):
- dt = datetime.now() + dt
- if isinstance(dt, (datetime, date)):
- dt = dt.timetuple()
- if isinstance(dt, (tuple, time.struct_time)):
- dt = calendar.timegm(dt)
- if not isinstance(dt, (float, int, long)):
- raise ValueError(
- "You must pass in a datetime, date, time tuple, or integer object, not %r" % dt)
- return formatdate(dt)
-
-def _serialize_cookie_date(dt):
- if dt is None:
- return None
- if isinstance(dt, unicode):
- dt = dt.encode('ascii')
- if isinstance(dt, timedelta):
- dt = datetime.now() + dt
- if isinstance(dt, (datetime, date)):
- dt = dt.timetuple()
- return time.strftime('%a, %d-%b-%Y %H:%M:%S GMT', dt)
-
-def _parse_date_delta(value):
- """
- like _parse_date, but also handle delta seconds
- """
- if not value:
- return None
- try:
- value = int(value)
- except ValueError:
- pass
- else:
- delta = timedelta(seconds=value)
- return datetime.now() + delta
- return _parse_date(value)
-
-def _serialize_date_delta(value):
- if not value and value != 0:
- return None
- if isinstance(value, (float, int)):
- return str(int(value))
- return _serialize_date(value)
-
-def _parse_etag(value, default=True):
- if value is None:
- value = ''
- value = value.strip()
- if not value:
- if default:
- return AnyETag
- else:
- return NoETag
- if value == '*':
- return AnyETag
- else:
- return ETagMatcher.parse(value)
-
-def _serialize_etag(value, default=True):
- if value is None:
- return None
- if value is AnyETag:
- if default:
- return None
- else:
- return '*'
- return str(value)
-
-def _parse_if_range(value):
- if not value:
- return NoIfRange
- else:
- return IfRange.parse(value)
-
-def _serialize_if_range(value):
- if value is None:
- return value
- if isinstance(value, (datetime, date)):
- return _serialize_date(value)
- if not isinstance(value, str):
- value = str(value)
- return value or None
-
-def _parse_range(value):
- if not value:
- return None
- # Might return None too:
- return Range.parse(value)
-
-def _serialize_range(value):
- if isinstance(value, (list, tuple)):
- if len(value) != 2:
- raise ValueError(
- "If setting .range to a list or tuple, it must be of length 2 (not %r)"
- % value)
- value = Range([value])
- if value is None:
- return None
- value = str(value)
- return value or None
-
-def _parse_int(value):
- if value is None or value == '':
- return None
- return int(value)
-
-def _parse_int_safe(value):
- if value is None or value == '':
- return None
- try:
- return int(value)
- except ValueError:
- return None
-
-def _serialize_int(value):
- if value is None:
- return None
- return str(value)
-
-def _parse_content_range(value):
- if not value or not value.strip():
- return None
- # May still return None
- return ContentRange.parse(value)
-
-def _serialize_content_range(value):
- if value is None:
- return None
- if isinstance(value, (tuple, list)):
- if len(value) not in (2, 3):
- raise ValueError(
- "When setting content_range to a list/tuple, it must "
- "be length 2 or 3 (not %r)" % value)
- if len(value) == 2:
- begin, end = value
- length = None
- else:
- begin, end, length = value
- value = ContentRange(begin, end, length)
- value = str(value).strip()
- if not value:
- return None
- return value
-
-def _parse_list(value):
- if value is None:
- return None
- value = value.strip()
- if not value:
- return None
- return [v.strip() for v in value.split(',')
- if v.strip()]
-
-def _serialize_list(value):
- if not value:
- return None
- if isinstance(value, unicode):
- value = str(value)
- if isinstance(value, str):
- return value
- return ', '.join(map(str, value))
-
-def _parse_accept(value, header_name, AcceptClass, NilClass):
- if not value:
- return NilClass(header_name)
- return AcceptClass(header_name, value)
-
-def _serialize_accept(value, header_name, AcceptClass, NilClass):
- if not value or isinstance(value, NilClass):
- return None
- if isinstance(value, (list, tuple, dict)):
- value = NilClass(header_name) + value
- value = str(value).strip()
- if not value:
- return None
- return value
-
-class Request(object):
-
- ## Options:
- charset = None
- unicode_errors = 'strict'
- decode_param_names = False
- ## The limit after which request bodies should be stored on disk
- ## if they are read in (under this, and the request body is stored
- ## in memory):
- request_body_tempfile_limit = 10*1024
-
- def __init__(self, environ=None, environ_getter=None, charset=NoDefault, unicode_errors=NoDefault,
- decode_param_names=NoDefault, **kw):
- if environ is None and environ_getter is None:
- raise TypeError(
- "You must provide one of environ or environ_getter")
- if environ is not None and environ_getter is not None:
- raise TypeError(
- "You can only provide one of the environ and environ_getter arguments")
- if environ is None:
- self._environ_getter = environ_getter
- else:
- if not isinstance(environ, dict):
- raise TypeError(
- "Bad type for environ: %s" % type(environ))
- self._environ = environ
- if charset is not NoDefault:
- self.__dict__['charset'] = charset
- if unicode_errors is not NoDefault:
- self.__dict__['unicode_errors'] = unicode_errors
- if decode_param_names is not NoDefault:
- self.__dict__['decode_param_names'] = decode_param_names
- for name, value in kw.items():
- if not hasattr(self.__class__, name):
- raise TypeError(
- "Unexpected keyword: %s=%r" % name, value)
- setattr(self, name, value)
-
- def __setattr__(self, attr, value, DEFAULT=[]):
- ## FIXME: I don't know why I need this guard (though experimentation says I do)
- if getattr(self.__class__, attr, DEFAULT) is not DEFAULT or attr.startswith('_'):
- object.__setattr__(self, attr, value)
- else:
- self.environ.setdefault('webob.adhoc_attrs', {})[attr] = value
-
- def __getattr__(self, attr):
- ## FIXME: I don't know why I need this guard (though experimentation says I do)
- if attr in self.__class__.__dict__:
- return object.__getattribute__(self, attr)
- try:
- return self.environ['webob.adhoc_attrs'][attr]
- except KeyError:
- raise AttributeError(attr)
-
- def __delattr__(self, attr):
- ## FIXME: I don't know why I need this guard (though experimentation says I do)
- if attr in self.__class__.__dict__:
- return object.__delattr__(self, attr)
- try:
- del self.environ['webob.adhoc_attrs'][attr]
- except KeyError:
- raise AttributeError(attr)
-
- def environ(self):
- """
- The WSGI environment dictionary for this request
- """
- return self._environ_getter()
- environ = property(environ, doc=environ.__doc__)
-
- def _environ_getter(self):
- return self._environ
-
- def _body_file__get(self):
- """
- Access the body of the request (wsgi.input) as a file-like
- object.
-
- If you set this value, CONTENT_LENGTH will also be updated
- (either set to -1, 0 if you delete the attribute, or if you
- set the attribute to a string then the length of the string).
- """
- return self.environ['wsgi.input']
- def _body_file__set(self, value):
- if isinstance(value, str):
- length = len(value)
- value = StringIO(value)
- else:
- length = -1
- self.environ['wsgi.input'] = value
- self.environ['CONTENT_LENGTH'] = str(length)
- def _body_file__del(self):
- self.environ['wsgi.input'] = StringIO('')
- self.environ['CONTENT_LENGTH'] = '0'
- body_file = property(_body_file__get, _body_file__set, _body_file__del, doc=_body_file__get.__doc__)
-
- scheme = environ_getter('wsgi.url_scheme')
- method = environ_getter('REQUEST_METHOD')
- script_name = environ_getter('SCRIPT_NAME')
- path_info = environ_getter('PATH_INFO')
- ## FIXME: should I strip out parameters?:
- content_type = environ_getter('CONTENT_TYPE', rfc_section='14.17')
- content_length = converter(
- environ_getter('CONTENT_LENGTH', rfc_section='14.13'),
- _parse_int_safe, _serialize_int, 'int')
- remote_user = environ_getter('REMOTE_USER', default=None)
- remote_addr = environ_getter('REMOTE_ADDR', default=None)
- query_string = environ_getter('QUERY_STRING')
- server_name = environ_getter('SERVER_NAME')
- server_port = converter(
- environ_getter('SERVER_PORT'),
- _parse_int, _serialize_int, 'int')
-
- _headers = None
-
- def _headers__get(self):
- """
- All the request headers as a case-insensitive dictionary-like
- object.
- """
- if self._headers is None:
- self._headers = EnvironHeaders(self.environ)
- return self._headers
-
- def _headers__set(self, value):
- self.headers.clear()
- self.headers.update(value)
-
- headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__)
-
- def host_url(self):
- """
- The URL through the host (no path)
- """
- e = self.environ
- url = e['wsgi.url_scheme'] + '://'
- if e.get('HTTP_HOST'):
- host = e['HTTP_HOST']
- if ':' in host:
- host, port = host.split(':', 1)
- else:
-
- port = None
- else:
- host = e['SERVER_NAME']
- port = e['SERVER_PORT']
- if self.environ['wsgi.url_scheme'] == 'https':
- if port == '443':
- port = None
- elif self.environ['wsgi.url_scheme'] == 'http':
- if port == '80':
- port = None
- url += host
- if port:
- url += ':%s' % port
- return url
- host_url = property(host_url, doc=host_url.__doc__)
-
- def application_url(self):
- """
- The URL including SCRIPT_NAME (no PATH_INFO or query string)
- """
- return self.host_url + urllib.quote(self.environ.get('SCRIPT_NAME', ''))
- application_url = property(application_url, doc=application_url.__doc__)
-
- def path_url(self):
- """
- The URL including SCRIPT_NAME and PATH_INFO, but not QUERY_STRING
- """
- return self.application_url + urllib.quote(self.environ.get('PATH_INFO', ''))
- path_url = property(path_url, doc=path_url.__doc__)
-
- def path(self):
- """
- The path of the request, without host or query string
- """
- return urllib.quote(self.script_name) + urllib.quote(self.path_info)
- path = property(path, doc=path.__doc__)
-
- def path_qs(self):
- """
- The path of the request, without host but with query string
- """
- path = self.path
- qs = self.environ.get('QUERY_STRING')
- if qs:
- path += '?' + qs
- return path
- path_qs = property(path_qs, doc=path_qs.__doc__)
-
- def url(self):
- """
- The full request URL, including QUERY_STRING
- """
- url = self.path_url
- if self.environ.get('QUERY_STRING'):
- url += '?' + self.environ['QUERY_STRING']
- return url
- url = property(url, doc=url.__doc__)
-
- def relative_url(self, other_url, to_application=False):
- """
- Resolve other_url relative to the request URL.
-
- If ``to_application`` is True, then resolve it relative to the
- URL with only SCRIPT_NAME
- """
- if to_application:
- url = self.application_url
- if not url.endswith('/'):
- url += '/'
- else:
- url = self.path_url
- return urlparse.urljoin(url, other_url)
-
- def path_info_pop(self):
- """
- 'Pops' off the next segment of PATH_INFO, pushing it onto
- SCRIPT_NAME, and returning the popped segment. Returns None if
- there is nothing left on PATH_INFO.
-
- Does not return ``''`` when there's an empty segment (like
- ``/path//path``); these segments are just ignored.
- """
- path = self.path_info
- if not path:
- return None
- while path.startswith('/'):
- self.script_name += '/'
- path = path[1:]
- if '/' not in path:
- self.script_name += path
- self.path_info = ''
- return path
- else:
- segment, path = path.split('/', 1)
- self.path_info = '/' + path
- self.script_name += segment
- return segment
-
- def path_info_peek(self):
- """
- Returns the next segment on PATH_INFO, or None if there is no
- next segment. Doesn't modify the environment.
- """
- path = self.path_info
- if not path:
- return None
- path = path.lstrip('/')
- return path.split('/', 1)[0]
-
- def _urlvars__get(self):
- """
- Return any *named* variables matched in the URL.
-
- Takes values from ``environ['wsgiorg.routing_args']``.
- Systems like ``routes`` set this value.
- """
- if 'paste.urlvars' in self.environ:
- return self.environ['paste.urlvars']
- elif 'wsgiorg.routing_args' in self.environ:
- return self.environ['wsgiorg.routing_args'][1]
- else:
- result = {}
- self.environ['wsgiorg.routing_args'] = ((), result)
- return result
-
- def _urlvars__set(self, value):
- environ = self.environ
- if 'wsgiorg.routing_args' in environ:
- environ['wsgiorg.routing_args'] = (environ['wsgiorg.routing_args'][0], value)
- if 'paste.urlvars' in environ:
- del environ['paste.urlvars']
- elif 'paste.urlvars' in environ:
- environ['paste.urlvars'] = value
- else:
- environ['wsgiorg.routing_args'] = ((), value)
-
- def _urlvars__del(self):
- if 'paste.urlvars' in self.environ:
- del self.environ['paste.urlvars']
- if 'wsgiorg.routing_args' in self.environ:
- if not self.environ['wsgiorg.routing_args'][0]:
- del self.environ['wsgiorg.routing_args']
- else:
- self.environ['wsgiorg.routing_args'] = (self.environ['wsgiorg.routing_args'][0], {})
-
- urlvars = property(_urlvars__get, _urlvars__set, _urlvars__del, doc=_urlvars__get.__doc__)
-
- def _urlargs__get(self):
- """
- Return any *positional* variables matched in the URL.
-
- Takes values from ``environ['wsgiorg.routing_args']``.
- Systems like ``routes`` set this value.
- """
- if 'wsgiorg.routing_args' in self.environ:
- return self.environ['wsgiorg.routing_args'][0]
- else:
- # Since you can't update this value in-place, we don't need
- # to set the key in the environment
- return ()
-
- def _urlargs__set(self, value):
- environ = self.environ
- if 'paste.urlvars' in environ:
- # Some overlap between this and wsgiorg.routing_args; we need
- # wsgiorg.routing_args to make this work
- routing_args = (value, environ.pop('paste.urlvars'))
- elif 'wsgiorg.routing_args' in environ:
- routing_args = (value, environ['wsgiorg.routing_args'][1])
- else:
- routing_args = (value, {})
- environ['wsgiorg.routing_args'] = routing_args
-
- def _urlargs__del(self):
- if 'wsgiorg.routing_args' in self.environ:
- if not self.environ['wsgiorg.routing_args'][1]:
- del self.environ['wsgiorg.routing_args']
- else:
- self.environ['wsgiorg.routing_args'] = ((), self.environ['wsgiorg.routing_args'][1])
-
- urlargs = property(_urlargs__get, _urlargs__set, _urlargs__del, _urlargs__get.__doc__)
-
- def is_xhr(self):
- """Returns a boolean if X-Requested-With is present and ``XMLHttpRequest``
-
- Note: this isn't set by every XMLHttpRequest request, it is
- only set if you are using a Javascript library that sets it
- (or you set the header yourself manually). Currently
- Prototype and jQuery are known to set this header."""
- return self.environ.get('HTTP_X_REQUESTED_WITH', '') == 'XMLHttpRequest'
- is_xhr = property(is_xhr, doc=is_xhr.__doc__)
-
- def _host__get(self):
- """Host name provided in HTTP_HOST, with fall-back to SERVER_NAME"""
- if 'HTTP_HOST' in self.environ:
- return self.environ['HTTP_HOST']
- else:
- return '%(SERVER_NAME)s:%(SERVER_PORT)s' % self.environ
- def _host__set(self, value):
- self.environ['HTTP_HOST'] = value
- def _host__del(self):
- if 'HTTP_HOST' in self.environ:
- del self.environ['HTTP_HOST']
- host = property(_host__get, _host__set, _host__del, doc=_host__get.__doc__)
-
- def _body__get(self):
- """
- Return the content of the request body.
- """
- try:
- length = int(self.environ.get('CONTENT_LENGTH', '0'))
- except ValueError:
- return ''
- c = self.body_file.read(length)
- tempfile_limit = self.request_body_tempfile_limit
- if tempfile_limit and len(c) > tempfile_limit:
- fileobj = tempfile.TemporaryFile()
- fileobj.write(c)
- fileobj.seek(0)
- else:
- fileobj = StringIO(c)
- # We don't want/need to lose CONTENT_LENGTH here (as setting
- # self.body_file would do):
- self.environ['wsgi.input'] = fileobj
- return c
-
- def _body__set(self, value):
- if value is None:
- del self.body
- return
- if not isinstance(value, str):
- raise TypeError(
- "You can only set Request.body to a str (not %r)" % type(value))
- body_file = StringIO(value)
- self.body_file = body_file
- self.environ['CONTENT_LENGTH'] = str(len(value))
-
- def _body__del(self, value):
- del self.body_file
-
- body = property(_body__get, _body__set, _body__del, doc=_body__get.__doc__)
-
- def str_POST(self):
- """
- Return a MultiDict containing all the variables from a form
- request. Returns an empty dict-like object for non-form
- requests.
-
- Form requests are typically POST requests, however PUT requests
- with an appropriate Content-Type are also supported.
- """
- env = self.environ
- if self.method not in ('POST', 'PUT'):
- return NoVars('Not a form request')
- if 'webob._parsed_post_vars' in env:
- vars, body_file = env['webob._parsed_post_vars']
- if body_file is self.body_file:
- return vars
- # Paste compatibility:
- if 'paste.parsed_formvars' in env:
- # from paste.request.parse_formvars
- vars, body_file = env['paste.parsed_formvars']
- if body_file is self.body_file:
- # FIXME: is it okay that this isn't *our* MultiDict?
- return vars
- content_type = self.content_type
- if ';' in content_type:
- content_type = content_type.split(';', 1)[0]
- if (self.method == 'PUT' and not content_type) or \
- content_type not in ('', 'application/x-www-form-urlencoded',
- 'multipart/form-data'):
- # Not an HTML form submission
- return NoVars('Not an HTML form submission (Content-Type: %s)'
- % content_type)
- if 'CONTENT_LENGTH' not in env:
- # FieldStorage assumes a default CONTENT_LENGTH of -1, but a
- # default of 0 is better:
- env['CONTENT_TYPE'] = '0'
- fs_environ = env.copy()
- fs_environ['QUERY_STRING'] = ''
- fs = cgi.FieldStorage(fp=self.body_file,
- environ=fs_environ,
- keep_blank_values=True)
- vars = MultiDict.from_fieldstorage(fs)
- FakeCGIBody.update_environ(env, vars)
- env['webob._parsed_post_vars'] = (vars, self.body_file)
- return vars
-
- str_POST = property(str_POST, doc=str_POST.__doc__)
-
- str_postvars = deprecated_property(str_POST, 'str_postvars',
- 'use str_POST instead')
-
- def POST(self):
- """
- Like ``.str_POST``, but may decode values and keys
- """
- vars = self.str_POST
- if self.charset:
- vars = UnicodeMultiDict(vars, encoding=self.charset,
- errors=self.unicode_errors,
- decode_keys=self.decode_param_names)
- return vars
-
- POST = property(POST, doc=POST.__doc__)
-
- postvars = deprecated_property(POST, 'postvars',
- 'use POST instead')
-
- def str_GET(self):
- """
- Return a MultiDict containing all the variables from the
- QUERY_STRING.
- """
- env = self.environ
- source = env.get('QUERY_STRING', '')
- if 'webob._parsed_query_vars' in env:
- vars, qs = env['webob._parsed_query_vars']
- if qs == source:
- return vars
- if not source:
- vars = MultiDict()
- else:
- vars = MultiDict(cgi.parse_qsl(
- source, keep_blank_values=True,
- strict_parsing=False))
- env['webob._parsed_query_vars'] = (vars, source)
- return vars
-
- str_GET = property(str_GET, doc=str_GET.__doc__)
-
- str_queryvars = deprecated_property(str_GET, 'str_queryvars',
- 'use str_GET instead')
-
-
- def GET(self):
- """
- Like ``.str_GET``, but may decode values and keys
- """
- vars = self.str_GET
- if self.charset:
- vars = UnicodeMultiDict(vars, encoding=self.charset,
- errors=self.unicode_errors,
- decode_keys=self.decode_param_names)
- return vars
-
- GET = property(GET, doc=GET.__doc__)
-
- queryvars = deprecated_property(GET, 'queryvars',
- 'use GET instead')
-
- def str_params(self):
- """
- A dictionary-like object containing both the parameters from
- the query string and request body.
- """
- return NestedMultiDict(self.str_GET, self.str_POST)
-
- str_params = property(str_params, doc=str_params.__doc__)
-
- def params(self):
- """
- Like ``.str_params``, but may decode values and keys
- """
- params = self.str_params
- if self.charset:
- params = UnicodeMultiDict(params, encoding=self.charset,
- errors=self.unicode_errors,
- decode_keys=self.decode_param_names)
- return params
-
- params = property(params, doc=params.__doc__)
-
- _rx_quotes = re.compile('"(.*)"')
-
- def str_cookies(self):
- """
- Return a *plain* dictionary of cookies as found in the request.
- """
- env = self.environ
- source = env.get('HTTP_COOKIE', '')
- if 'webob._parsed_cookies' in env:
- vars, var_source = env['webob._parsed_cookies']
- if var_source == source:
- return vars
- vars = {}
- if source:
- cookies = BaseCookie()
- cookies.load(source)
- for name in cookies:
- value = cookies[name].value
- unquote_match = self._rx_quotes.match(value)
- if unquote_match is not None:
- value = unquote_match.group(1)
- vars[name] = value
- env['webob._parsed_cookies'] = (vars, source)
- return vars
-
- str_cookies = property(str_cookies, doc=str_cookies.__doc__)
-
- def cookies(self):
- """
- Like ``.str_cookies``, but may decode values and keys
- """
- vars = self.str_cookies
- if self.charset:
- vars = UnicodeMultiDict(vars, encoding=self.charset,
- errors=self.unicode_errors,
- decode_keys=self.decode_param_names)
- return vars
-
- cookies = property(cookies, doc=cookies.__doc__)
-
- def copy(self):
- """
- Copy the request and environment object.
-
- This only does a shallow copy, except of wsgi.input
- """
- env = self.environ.copy()
- new_req = self.__class__(env)
- new_req.copy_body()
- return new_req
-
- def copy_get(self):
- """
- Copies the request and environment object, but turning this request
- into a GET along the way. If this was a POST request (or any other verb)
- then it becomes GET, and the request body is thrown away.
- """
- env = self.environ.copy()
- env['wsgi.input'] = StringIO('')
- env['CONTENT_LENGTH'] = '0'
- if 'CONTENT_TYPE' in env:
- del env['CONTENT_TYPE']
- env['REQUEST_METHOD'] = 'GET'
- return self.__class__(env)
-
- def make_body_seekable(self):
- """
- This forces ``environ['wsgi.input']`` to be seekable. That
- is, if it doesn't have a `seek` method already, the content is
- copied into a StringIO or temporary file.
-
- The choice to copy to StringIO is made from
- ``self.request_body_tempfile_limit``
- """
- input = self.body_file
- if hasattr(input, 'seek'):
- # It has a seek method, so we don't need to do anything
- return
- self.copy_body()
-
- def copy_body(self):
- """
- Copies the body, in cases where it might be shared with
- another request object and that is not desired.
-
- This copies the body in-place, either into a StringIO object
- or a temporary file.
- """
- length = self.content_length
- if length == 0:
- # No real need to copy this, but of course it is free
- self.body_file = StringIO('')
- return
- tempfile_limit = self.request_body_tempfile_limit
- body = None
- input = self.body_file
- if hasattr(input, 'seek'):
- # Just in case someone has read parts of the body already
- ## FIXME: Should we use .tell() to try to put the body
- ## back to its previous position?
- input.seek(0)
- if length == -1:
- body = self.body
- length = len(body)
- self.content_length = length
- if tempfile_limit and length > tempfile_limit:
- fileobj = tempfile.TemporaryFile()
- if body is None:
- while length:
- data = input.read(min(length, 4096))
- fileobj.write(data)
- length -= len(data)
- else:
- fileobj.write(body)
- fileobj.seek(0)
- else:
- if body is None:
- body = input.read(length)
- fileobj = StringIO(body)
- self.body_file = fileobj
-
- def remove_conditional_headers(self, remove_encoding=True, remove_range=True,
- remove_match=True, remove_modified=True):
- """
- Remove headers that make the request conditional.
-
- These headers can cause the response to be 304 Not Modified,
- which in some cases you may not want to be possible.
-
- This does not remove headers like If-Match, which are used for
- conflict detection.
- """
- check_keys = []
- if remove_range:
- check_keys += ['HTTP_IF_RANGE', 'HTTP_RANGE']
- if remove_match:
- check_keys.append('HTTP_IF_NONE_MATCH')
- if remove_modified:
- check_keys.append('HTTP_IF_MODIFIED_SINCE')
- if remove_encoding:
- check_keys.append('HTTP_ACCEPT_ENCODING')
-
- for key in check_keys:
- if key in self.environ:
- del self.environ[key]
-
- accept = converter(
- environ_getter('HTTP_ACCEPT', rfc_section='14.1'),
- _parse_accept, _serialize_accept, 'MIME Accept',
- converter_args=('Accept', MIMEAccept, MIMENilAccept))
-
- accept_charset = converter(
- environ_getter('HTTP_ACCEPT_CHARSET', rfc_section='14.2'),
- _parse_accept, _serialize_accept, 'accept header',
- converter_args=('Accept-Charset', Accept, NilAccept))
-
- accept_encoding = converter(
- environ_getter('HTTP_ACCEPT_ENCODING', rfc_section='14.3'),
- _parse_accept, _serialize_accept, 'accept header',
- converter_args=('Accept-Encoding', Accept, NoAccept))
-
- accept_language = converter(
- environ_getter('HTTP_ACCEPT_LANGUAGE', rfc_section='14.4'),
- _parse_accept, _serialize_accept, 'accept header',
- converter_args=('Accept-Language', Accept, NilAccept))
-
- ## FIXME: 14.8 Authorization
- ## http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.8
-
- def _cache_control__get(self):
- """
- Get/set/modify the Cache-Control header (section `14.9
- <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9>`_)
- """
- env = self.environ
- value = env.get('HTTP_CACHE_CONTROL', '')
- cache_header, cache_obj = env.get('webob._cache_control', (None, None))
- if cache_obj is not None and cache_header == value:
- return cache_obj
- cache_obj = CacheControl.parse(value, type='request')
- env['webob._cache_control'] = (value, cache_obj)
- return cache_obj
-
- def _cache_control__set(self, value):
- env = self.environ
- if not value:
- value = ""
- if isinstance(value, dict):
- value = CacheControl(value, type='request')
- elif isinstance(value, CacheControl):
- str_value = str(value)
- env['HTTP_CACHE_CONTROL'] = str_value
- env['webob._cache_control'] = (str_value, value)
- else:
- env['HTTP_CACHE_CONTROL'] = str(value)
- if 'webob._cache_control' in env:
- del env['webob._cache_control']
-
- def _cache_control__del(self, value):
- env = self.environ
- if 'HTTP_CACHE_CONTROL' in env:
- del env['HTTP_CACHE_CONTROL']
- if 'webob._cache_control' in env:
- del env['webob._cache_control']
-
- cache_control = property(_cache_control__get, _cache_control__set, _cache_control__del, doc=_cache_control__get.__doc__)
-
- date = converter(
- environ_getter('HTTP_DATE', rfc_section='14.8'),
- _parse_date, _serialize_date, 'HTTP date')
-
- if_match = converter(
- environ_getter('HTTP_IF_MATCH', rfc_section='14.24'),
- _parse_etag, _serialize_etag, 'ETag', converter_args=(True,))
-
- if_modified_since = converter(
- environ_getter('HTTP_IF_MODIFIED_SINCE', rfc_section='14.25'),
- _parse_date, _serialize_date, 'HTTP date')
-
- if_none_match = converter(
- environ_getter('HTTP_IF_NONE_MATCH', rfc_section='14.26'),
- _parse_etag, _serialize_etag, 'ETag', converter_args=(False,))
-
- if_range = converter(
- environ_getter('HTTP_IF_RANGE', rfc_section='14.27'),
- _parse_if_range, _serialize_if_range, 'IfRange object')
-
- if_unmodified_since = converter(
- environ_getter('HTTP_IF_UNMODIFIED_SINCE', rfc_section='14.28'),
- _parse_date, _serialize_date, 'HTTP date')
-
- max_forwards = converter(
- environ_getter('HTTP_MAX_FORWARDS', rfc_section='14.31'),
- _parse_int, _serialize_int, 'int')
-
- pragma = environ_getter('HTTP_PRAGMA', rfc_section='14.32')
-
- range = converter(
- environ_getter('HTTP_RANGE', rfc_section='14.35'),
- _parse_range, _serialize_range, 'Range object')
-
- referer = environ_getter('HTTP_REFERER', rfc_section='14.36')
- referrer = referer
-
- user_agent = environ_getter('HTTP_USER_AGENT', rfc_section='14.43')
-
- def __repr__(self):
- msg = '<%s at %x %s %s>' % (
- self.__class__.__name__,
- abs(id(self)), self.method, self.url)
- return msg
-
- def __str__(self):
- url = self.url
- host = self.host_url
- assert url.startswith(host)
- url = url[len(host):]
- if 'Host' not in self.headers:
- self.headers['Host'] = self.host
- parts = ['%s %s' % (self.method, url)]
- for name, value in sorted(self.headers.items()):
- parts.append('%s: %s' % (name, value))
- parts.append('')
- parts.append(self.body)
- return '\r\n'.join(parts)
-
- def call_application(self, application, catch_exc_info=False):
- """
- Call the given WSGI application, returning ``(status_string,
- headerlist, app_iter)``
-
- Be sure to call ``app_iter.close()`` if it's there.
-
- If catch_exc_info is true, then returns ``(status_string,
- headerlist, app_iter, exc_info)``, where the fourth item may
- be None, but won't be if there was an exception. If you don't
- do this and there was an exception, the exception will be
- raised directly.
- """
- captured = []
- output = []
- def start_response(status, headers, exc_info=None):
- if exc_info is not None and not catch_exc_info:
- raise exc_info[0], exc_info[1], exc_info[2]
- captured[:] = [status, headers, exc_info]
- return output.append
- app_iter = application(self.environ, start_response)
- if (not captured
- or output):
- try:
- output.extend(app_iter)
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
- app_iter = output
- if catch_exc_info:
- return (captured[0], captured[1], app_iter, captured[2])
- else:
- return (captured[0], captured[1], app_iter)
-
- # Will be filled in later:
- ResponseClass = None
-
- def get_response(self, application, catch_exc_info=False):
- """
- Like ``.call_application(application)``, except returns a
- response object with ``.status``, ``.headers``, and ``.body``
- attributes.
-
- This will use ``self.ResponseClass`` to figure out the class
- of the response object to return.
- """
- if catch_exc_info:
- status, headers, app_iter, exc_info = self.call_application(
- application, catch_exc_info=True)
- del exc_info
- else:
- status, headers, app_iter = self.call_application(
- application, catch_exc_info=False)
- return self.ResponseClass(
- status=status, headerlist=headers, app_iter=app_iter,
- request=self)
-
- #@classmethod
- def blank(cls, path, environ=None, base_url=None, headers=None, **kw):
- """
- Create a blank request environ (and Request wrapper) with the
- given path (path should be urlencoded), and any keys from
- environ.
-
- The path will become path_info, with any query string split
- off and used.
-
- All necessary keys will be added to the environ, but the
- values you pass in will take precedence. If you pass in
- base_url then wsgi.url_scheme, HTTP_HOST, and SCRIPT_NAME will
- be filled in from that value.
-
- Any extra keyword will be passed to ``__init__`` (e.g.,
- ``decode_param_names``).
- """
- if _SCHEME_RE.search(path):
- scheme, netloc, path, qs, fragment = urlparse.urlsplit(path)
- if fragment:
- raise TypeError(
- "Path cannot contain a fragment (%r)" % fragment)
- if qs:
- path += '?' + qs
- if ':' not in netloc:
- if scheme == 'http':
- netloc += ':80'
- elif scheme == 'https':
- netloc += ':443'
- else:
- raise TypeError("Unknown scheme: %r" % scheme)
- else:
- scheme = 'http'
- netloc = 'localhost:80'
- if path and '?' in path:
- path_info, query_string = path.split('?', 1)
- path_info = urllib.unquote(path_info)
- else:
- path_info = urllib.unquote(path)
- query_string = ''
- env = {
- 'REQUEST_METHOD': 'GET',
- 'SCRIPT_NAME': '',
- 'PATH_INFO': path_info or '',
- 'QUERY_STRING': query_string,
- 'SERVER_NAME': netloc.split(':')[0],
- 'SERVER_PORT': netloc.split(':')[1],
- 'HTTP_HOST': netloc,
- 'SERVER_PROTOCOL': 'HTTP/1.0',
- 'wsgi.version': (1, 0),
- 'wsgi.url_scheme': scheme,
- 'wsgi.input': StringIO(''),
- 'wsgi.errors': sys.stderr,
- 'wsgi.multithread': False,
- 'wsgi.multiprocess': False,
- 'wsgi.run_once': False,
- }
- if base_url:
- scheme, netloc, path, query, fragment = urlparse.urlsplit(base_url)
- if query or fragment:
- raise ValueError(
- "base_url (%r) cannot have a query or fragment"
- % base_url)
- if scheme:
- env['wsgi.url_scheme'] = scheme
- if netloc:
- if ':' not in netloc:
- if scheme == 'http':
- netloc += ':80'
- elif scheme == 'https':
- netloc += ':443'
- else:
- raise ValueError(
- "Unknown scheme: %r" % scheme)
- host, port = netloc.split(':', 1)
- env['SERVER_PORT'] = port
- env['SERVER_NAME'] = host
- env['HTTP_HOST'] = netloc
- if path:
- env['SCRIPT_NAME'] = urllib.unquote(path)
- if environ:
- env.update(environ)
- obj = cls(env, **kw)
- if headers is not None:
- obj.headers.update(headers)
- return obj
-
- blank = classmethod(blank)
-
-class Response(object):
-
- """
- Represents a WSGI response
- """
-
- default_content_type = 'text/html'
- default_charset = 'UTF-8'
- unicode_errors = 'strict'
- default_conditional_response = False
-
- def __init__(self, body=None, status='200 OK', headerlist=None, app_iter=None,
- request=None, content_type=None, conditional_response=NoDefault,
- **kw):
- if app_iter is None:
- if body is None:
- body = ''
- elif body is not None:
- raise TypeError(
- "You may only give one of the body and app_iter arguments")
- self.status = status
- if headerlist is None:
- self._headerlist = []
- else:
- self._headerlist = headerlist
- self._headers = None
- if request is not None:
- if hasattr(request, 'environ'):
- self._environ = request.environ
- self._request = request
- else:
- self._environ = request
- self._request = None
- else:
- self._environ = self._request = None
- if content_type is not None:
- self.content_type = content_type
- elif self.default_content_type is not None and headerlist is None:
- self.content_type = self.default_content_type
- if conditional_response is NoDefault:
- self.conditional_response = self.default_conditional_response
- else:
- self.conditional_response = conditional_response
- if 'charset' in kw:
- # We set this early, so something like unicode_body works later
- value = kw.pop('charset')
- if value:
- self.charset = value
- elif self.default_charset and not self.charset and headerlist is None:
- ct = self.content_type
- if ct and (ct.startswith('text/') or ct.startswith('application/xml')
- or (ct.startswith('application/') and ct.endswith('+xml'))):
- self.charset = self.default_charset
- if app_iter is not None:
- self._app_iter = app_iter
- self._body = None
- else:
- if isinstance(body, unicode):
- self.unicode_body = body
- else:
- self.body = body
- self._app_iter = None
- for name, value in kw.items():
- if not hasattr(self.__class__, name):
- # Not a basic attribute
- raise TypeError(
- "Unexpected keyword: %s=%r" % (name, value))
- setattr(self, name, value)
-
- def __repr__(self):
- return '<%s %x %s>' % (
- self.__class__.__name__,
- abs(id(self)),
- self.status)
-
- def __str__(self):
- return (self.status + '\n'
- + '\n'.join(['%s: %s' % (name, value)
- for name, value in self.headerlist])
- + '\n\n'
- + self.body)
-
- def _status__get(self):
- """
- The status string
- """
- return self._status
-
- def _status__set(self, value):
- if isinstance(value, int):
- value = str(value)
- if not isinstance(value, str):
- raise TypeError(
- "You must set status to a string or integer (not %s)"
- % type(value))
- if ' ' not in value:
- # Need to add a reason:
- code = int(value)
- reason = status_reasons[code]
- value += ' ' + reason
- self._status = value
-
- status = property(_status__get, _status__set, doc=_status__get.__doc__)
-
- def _status_int__get(self):
- """
- The status as an integer
- """
- return int(self.status.split()[0])
- def _status_int__set(self, value):
- self.status = value
- status_int = property(_status_int__get, _status_int__set, doc=_status_int__get.__doc__)
-
- def _headerlist__get(self):
- """
- The list of response headers
- """
- return self._headerlist
-
- def _headerlist__set(self, value):
- self._headers = None
- if not isinstance(value, list):
- if hasattr(value, 'items'):
- value = value.items()
- value = list(value)
- self._headerlist = value
-
- def _headerlist__del(self):
- self.headerlist = []
-
- headerlist = property(_headerlist__get, _headerlist__set, _headerlist__del, doc=_headerlist__get.__doc__)
-
- def _charset__get(self):
- """
- Get/set the charset (in the Content-Type)
- """
- header = self.headers.get('content-type')
- if not header:
- return None
- match = _CHARSET_RE.search(header)
- if match:
- return match.group(1)
- return None
-
- def _charset__set(self, charset):
- if charset is None:
- del self.charset
- return
- try:
- header = self.headers.pop('content-type')
- except KeyError:
- raise AttributeError(
- "You cannot set the charset when no content-type is defined")
- match = _CHARSET_RE.search(header)
- if match:
- header = header[:match.start()] + header[match.end():]
- header += '; charset=%s' % charset
- self.headers['content-type'] = header
-
- def _charset__del(self):
- try:
- header = self.headers.pop('content-type')
- except KeyError:
- # Don't need to remove anything
- return
- match = _CHARSET_RE.search(header)
- if match:
- header = header[:match.start()] + header[match.end():]
- self.headers['content-type'] = header
-
- charset = property(_charset__get, _charset__set, _charset__del, doc=_charset__get.__doc__)
-
- def _content_type__get(self):
- """
- Get/set the Content-Type header (or None), *without* the
- charset or any parameters.
-
- If you include parameters (or ``;`` at all) when setting the
- content_type, any existing parameters will be deleted;
- otherwise they will be preserved.
- """
- header = self.headers.get('content-type')
- if not header:
- return None
- return header.split(';', 1)[0]
-
- def _content_type__set(self, value):
- if ';' not in value:
- header = self.headers.get('content-type', '')
- if ';' in header:
- params = header.split(';', 1)[1]
- value += ';' + params
- self.headers['content-type'] = value
-
- def _content_type__del(self):
- try:
- del self.headers['content-type']
- except KeyError:
- pass
-
- content_type = property(_content_type__get, _content_type__set,
- _content_type__del, doc=_content_type__get.__doc__)
-
- def _content_type_params__get(self):
- """
- Returns a dictionary of all the parameters in the content type.
- """
- params = self.headers.get('content-type', '')
- if ';' not in params:
- return {}
- params = params.split(';', 1)[1]
- result = {}
- for match in _PARAM_RE.finditer(params):
- result[match.group(1)] = match.group(2) or match.group(3) or ''
- return result
-
- def _content_type_params__set(self, value_dict):
- if not value_dict:
- del self.content_type_params
- return
- params = []
- for k, v in sorted(value_dict.items()):
- if not _OK_PARAM_RE.search(v):
- ## FIXME: I'm not sure what to do with "'s in the parameter value
- ## I think it might be simply illegal
- v = '"%s"' % v.replace('"', '\\"')
- params.append('; %s=%s' % (k, v))
- ct = self.headers.pop('content-type', '').split(';', 1)[0]
- ct += ''.join(params)
- self.headers['content-type'] = ct
-
- def _content_type_params__del(self, value):
- self.headers['content-type'] = self.headers.get('content-type', '').split(';', 1)[0]
-
- content_type_params = property(_content_type_params__get, _content_type_params__set, _content_type_params__del, doc=_content_type_params__get.__doc__)
-
- def _headers__get(self):
- """
- The headers in a dictionary-like object
- """
- if self._headers is None:
- self._headers = HeaderDict.view_list(self.headerlist)
- return self._headers
-
- def _headers__set(self, value):
- if hasattr(value, 'items'):
- value = value.items()
- self.headerlist = value
- self._headers = None
-
- headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__)
-
- def _body__get(self):
- """
- The body of the response, as a ``str``. This will read in the
- entire app_iter if necessary.
- """
- if self._body is None:
- if self._app_iter is None:
- raise AttributeError(
- "No body has been set")
- try:
- self._body = ''.join(self._app_iter)
- finally:
- if hasattr(self._app_iter, 'close'):
- self._app_iter.close()
- self._app_iter = None
- self.content_length = len(self._body)
- return self._body
-
- def _body__set(self, value):
- if isinstance(value, unicode):
- raise TypeError(
- "You cannot set Response.body to a unicode object (use Response.unicode_body)")
- if not isinstance(value, str):
- raise TypeError(
- "You can only set the body to a str (not %s)"
- % type(value))
- try:
- if self._body or self._app_iter:
- self.content_md5 = None
- except AttributeError:
- # if setting body early in initialization _body and _app_iter don't exist yet
- pass
- self._body = value
- self.content_length = len(value)
- self._app_iter = None
-
- def _body__del(self):
- self._body = None
- self.content_length = None
- self._app_iter = None
-
- body = property(_body__get, _body__set, _body__del, doc=_body__get.__doc__)
-
- def _body_file__get(self):
- """
- Returns a file-like object that can be used to write to the
- body. If you passed in a list app_iter, that app_iter will be
- modified by writes.
- """
- return ResponseBodyFile(self)
-
- def _body_file__del(self):
- del self.body
-
- body_file = property(_body_file__get, fdel=_body_file__del, doc=_body_file__get.__doc__)
-
- def write(self, text):
- if isinstance(text, unicode):
- self.unicode_body += text
- else:
- self.body += text
-
- def _unicode_body__get(self):
- """
- Get/set the unicode value of the body (using the charset of the Content-Type)
- """
- if not self.charset:
- raise AttributeError(
- "You cannot access Response.unicode_body unless charset is set")
- body = self.body
- return body.decode(self.charset, self.unicode_errors)
-
- def _unicode_body__set(self, value):
- if not self.charset:
- raise AttributeError(
- "You cannot access Response.unicode_body unless charset is set")
- if not isinstance(value, unicode):
- raise TypeError(
- "You can only set Response.unicode_body to a unicode string (not %s)" % type(value))
- self.body = value.encode(self.charset)
-
- def _unicode_body__del(self):
- del self.body
-
- unicode_body = property(_unicode_body__get, _unicode_body__set, _unicode_body__del, doc=_unicode_body__get.__doc__)
-
- def _app_iter__get(self):
- """
- Returns the app_iter of the response.
-
- If body was set, this will create an app_iter from that body
- (a single-item list)
- """
- if self._app_iter is None:
- if self._body is None:
- raise AttributeError(
- "No body or app_iter has been set")
- return [self._body]
- else:
- return self._app_iter
-
- def _app_iter__set(self, value):
- if self._body is not None:
- # Undo the automatically-set content-length
- self.content_length = None
- self._app_iter = value
- self._body = None
-
- def _app_iter__del(self):
- self.content_length = None
- self._app_iter = self._body = None
-
- app_iter = property(_app_iter__get, _app_iter__set, _app_iter__del, doc=_app_iter__get.__doc__)
-
- def set_cookie(self, key, value='', max_age=None,
- path='/', domain=None, secure=None, httponly=False,
- version=None, comment=None, expires=None):
- """
- Set (add) a cookie for the response
- """
- if isinstance(value, unicode) and self.charset is not None:
- value = '"%s"' % value.encode(self.charset)
- cookies = BaseCookie()
- cookies[key] = value
- if isinstance(max_age, timedelta):
- max_age = max_age.seconds + max_age.days*24*60*60
- if max_age is not None and expires is None:
- expires = datetime.utcnow() + timedelta(seconds=max_age)
- if isinstance(expires, timedelta):
- expires = datetime.utcnow() + expires
- if isinstance(expires, datetime):
- expires = '"'+_serialize_cookie_date(expires)+'"'
- for var_name, var_value in [
- ('max_age', max_age),
- ('path', path),
- ('domain', domain),
- ('secure', secure),
- ('HttpOnly', httponly),
- ('version', version),
- ('comment', comment),
- ('expires', expires),
- ]:
- if var_value is not None and var_value is not False:
- cookies[key][var_name.replace('_', '-')] = str(var_value)
- header_value = cookies[key].output(header='').lstrip()
- if header_value.endswith(';'):
- # Python 2.4 adds a trailing ; to the end, strip it to be
- # consistent with 2.5
- header_value = header_value[:-1]
- self.headerlist.append(('Set-Cookie', header_value))
-
- def delete_cookie(self, key, path='/', domain=None):
- """
- Delete a cookie from the client. Note that path and domain must match
- how the cookie was originally set.
-
- This sets the cookie to the empty string, and max_age=0 so
- that it should expire immediately.
- """
- self.set_cookie(key, '', path=path, domain=domain,
- max_age=0, expires=timedelta(days=-5))
-
- def unset_cookie(self, key):
- """
- Unset a cookie with the given name (remove it from the
- response). If there are multiple cookies (e.g., two cookies
- with the same name and different paths or domains), all such
- cookies will be deleted.
- """
- existing = self.headers.getall('Set-Cookie')
- if not existing:
- raise KeyError(
- "No cookies at all have been set")
- del self.headers['Set-Cookie']
- found = False
- for header in existing:
- cookies = BaseCookie()
- cookies.load(header)
- if key in cookies:
- found = True
- del cookies[key]
- header = cookies.output(header='').lstrip()
- if header:
- if header.endswith(';'):
- # Python 2.4 adds a trailing ; to the end, strip it
- # to be consistent with 2.5
- header = header[:-1]
- self.headers.add('Set-Cookie', header)
- if not found:
- raise KeyError(
- "No cookie has been set with the name %r" % key)
-
- def _location__get(self):
- """
- Retrieve the Location header of the response, or None if there
- is no header. If the header is not absolute and this response
- is associated with a request, make the header absolute.
-
- For more information see `section 14.30
- <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30>`_.
- """
- if 'location' not in self.headers:
- return None
- location = self.headers['location']
- if _SCHEME_RE.search(location):
- # Absolute
- return location
- if self.request is not None:
- base_uri = self.request.url
- location = urlparse.urljoin(base_uri, location)
- return location
-
- def _location__set(self, value):
- if not _SCHEME_RE.search(value):
- # Not absolute, see if we can make it absolute
- if self.request is not None:
- value = urlparse.urljoin(self.request.url, value)
- self.headers['location'] = value
-
- def _location__del(self):
- if 'location' in self.headers:
- del self.headers['location']
-
- location = property(_location__get, _location__set, _location__del, doc=_location__get.__doc__)
-
- accept_ranges = header_getter('Accept-Ranges', rfc_section='14.5')
-
- age = converter(
- header_getter('Age', rfc_section='14.6'),
- _parse_int_safe, _serialize_int, 'int')
-
- allow = converter(
- header_getter('Allow', rfc_section='14.7'),
- _parse_list, _serialize_list, 'list')
-
- _cache_control_obj = None
-
- def _cache_control__get(self):
- """
- Get/set/modify the Cache-Control header (section `14.9
- <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9>`_)
- """
- value = self.headers.get('cache-control', '')
- if self._cache_control_obj is None:
- self._cache_control_obj = CacheControl.parse(value, updates_to=self._update_cache_control, type='response')
- self._cache_control_obj.header_value = value
- if self._cache_control_obj.header_value != value:
- new_obj = CacheControl.parse(value, type='response')
- self._cache_control_obj.properties.clear()
- self._cache_control_obj.properties.update(new_obj.properties)
- self._cache_control_obj.header_value = value
- return self._cache_control_obj
-
- def _cache_control__set(self, value):
- # This actually becomes a copy
- if not value:
- value = ""
- if isinstance(value, dict):
- value = CacheControl(value, 'response')
- if isinstance(value, unicode):
- value = str(value)
- if isinstance(value, str):
- if self._cache_control_obj is None:
- self.headers['Cache-Control'] = value
- return
- value = CacheControl.parse(value, 'response')
- cache = self.cache_control
- cache.properties.clear()
- cache.properties.update(value.properties)
-
- def _cache_control__del(self):
- self.cache_control = {}
-
- def _update_cache_control(self, prop_dict):
- value = serialize_cache_control(prop_dict)
- if not value:
- if 'Cache-Control' in self.headers:
- del self.headers['Cache-Control']
- else:
- self.headers['Cache-Control'] = value
-
- cache_control = property(_cache_control__get, _cache_control__set, _cache_control__del, doc=_cache_control__get.__doc__)
-
- def cache_expires(self, seconds=0, **kw):
- """
- Set expiration on this request. This sets the response to
- expire in the given seconds, and any other attributes are used
- for cache_control (e.g., private=True, etc).
- """
- cache_control = self.cache_control
- if isinstance(seconds, timedelta):
- seconds = timedelta_to_seconds(seconds)
- if not seconds:
- # To really expire something, you have to force a
- # bunch of these cache control attributes, and IE may
- # not pay attention to those still so we also set
- # Expires.
- cache_control.no_store = True
- cache_control.no_cache = True
- cache_control.must_revalidate = True
- cache_control.max_age = 0
- cache_control.post_check = 0
- cache_control.pre_check = 0
- self.expires = datetime.utcnow()
- if 'last-modified' not in self.headers:
- self.last_modified = datetime.utcnow()
- self.pragma = 'no-cache'
- else:
- cache_control.max_age = seconds
- self.expires = datetime.utcnow() + timedelta(seconds=seconds)
- for name, value in kw.items():
- setattr(cache_control, name, value)
-
- content_encoding = header_getter('Content-Encoding', rfc_section='14.11')
-
- def encode_content(self, encoding='gzip'):
- """
- Encode the content with the given encoding (only gzip and
- identity are supported).
- """
- if encoding == 'identity':
- self.decode_content()
- return
- if encoding != 'gzip':
- raise ValueError(
- "Unknown encoding: %r" % encoding)
- if self.content_encoding:
- if self.content_encoding == encoding:
- return
- self.decode_content()
- from webob.util.safegzip import GzipFile
- f = StringIO()
- gzip_f = GzipFile(filename='', mode='w', fileobj=f)
- gzip_f.write(self.body)
- gzip_f.close()
- new_body = f.getvalue()
- f.close()
- self.content_encoding = 'gzip'
- self.body = new_body
-
- def decode_content(self):
- content_encoding = self.content_encoding
- if not content_encoding or content_encoding == 'identity':
- return
- if content_encoding != 'gzip':
- raise ValueError(
- "I don't know how to decode the content %s" % content_encoding)
- from webob.util.safegzip import GzipFile
- f = StringIO(self.body)
- gzip_f = GzipFile(filename='', mode='r', fileobj=f)
- new_body = gzip_f.read()
- gzip_f.close()
- f.close()
- self.content_encoding = None
- self.body = new_body
-
- content_language = converter(
- header_getter('Content-Language', rfc_section='14.12'),
- _parse_list, _serialize_list, 'list')
-
- content_location = header_getter(
- 'Content-Location', rfc_section='14.14')
-
- content_md5 = header_getter(
- 'Content-MD5', rfc_section='14.14')
-
- content_range = converter(
- header_getter('Content-Range', rfc_section='14.16'),
- _parse_content_range, _serialize_content_range, 'ContentRange object')
-
- content_length = converter(
- header_getter('Content-Length', rfc_section='14.17'),
- _parse_int, _serialize_int, 'int')
-
- date = converter(
- header_getter('Date', rfc_section='14.18'),
- _parse_date, _serialize_date, 'HTTP date')
-
- etag = header_getter('ETag', rfc_section='14.19')
-
- def md5_etag(self, body=None, set_content_md5=False, set_conditional_response=False):
- """
- Generate an etag for the response object using an MD5 hash of
- the body (the body parameter, or ``self.body`` if not given)
-
- Sets ``self.etag``
- If ``set_content_md5`` is True sets ``self.content_md5`` as well
- If ``set_conditional_response`` is True sets ``self.conditional_response`` to True
- """
- if body is None:
- body = self.body
- try:
- from hashlib import md5
- except ImportError:
- from md5 import md5
- h = md5(body)
- md5_digest = h.digest().encode('base64').replace('\n', '').strip('=')
- self.etag = md5_digest
- if set_content_md5:
- self.content_md5 = md5_digest
- if set_conditional_response:
- self.conditional_response = True
-
- expires = converter(
- header_getter('Expires', rfc_section='14.21'),
- _parse_date, _serialize_date, 'HTTP date')
-
- last_modified = converter(
- header_getter('Last-Modified', rfc_section='14.29'),
- _parse_date, _serialize_date, 'HTTP date')
-
- pragma = header_getter('Pragma', rfc_section='14.32')
-
- retry_after = converter(
- header_getter('Retry-After', rfc_section='14.37'),
- _parse_date_delta, _serialize_date_delta, 'HTTP date or delta seconds')
-
- server = header_getter('Server', rfc_section='14.38')
-
- ## FIXME: I realize response.vary += 'something' won't work. It should.
- ## Maybe for all listy headers.
- vary = converter(
- header_getter('Vary', rfc_section='14.44'),
- _parse_list, _serialize_list, 'list')
-
- ## FIXME: 14.47 WWW-Authenticate
- ## http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.47
-
-
- def _request__get(self):
- """
- Return the request associated with this response if any.
- """
- if self._request is None and self._environ is not None:
- self._request = self.RequestClass(self._environ)
- return self._request
-
- def _request__set(self, value):
- if value is None:
- del self.request
- return
- if isinstance(value, dict):
- self._environ = value
- self._request = None
- else:
- self._request = value
- self._environ = value.environ
-
- def _request__del(self):
- self._request = self._environ = None
-
- request = property(_request__get, _request__set, _request__del, doc=_request__get.__doc__)
-
- def _environ__get(self):
- """
- Get/set the request environ associated with this response, if
- any.
- """
- return self._environ
-
- def _environ__set(self, value):
- if value is None:
- del self.environ
- self._environ = value
- self._request = None
-
- def _environ__del(self):
- self._request = self._environ = None
-
- environ = property(_environ__get, _environ__set, _environ__del, doc=_environ__get.__doc__)
-
- def __call__(self, environ, start_response):
- """
- WSGI application interface
- """
- if self.conditional_response:
- return self.conditional_response_app(environ, start_response)
- start_response(self.status, self.headerlist)
- if environ['REQUEST_METHOD'] == 'HEAD':
- # Special case here...
- return []
- return self.app_iter
-
- _safe_methods = ('GET', 'HEAD')
-
- def conditional_response_app(self, environ, start_response):
- """
- Like the normal __call__ interface, but checks conditional headers:
-
- * If-Modified-Since (304 Not Modified; only on GET, HEAD)
- * If-None-Match (304 Not Modified; only on GET, HEAD)
- * Range (406 Partial Content; only on GET, HEAD)
- """
- req = self.RequestClass(environ)
- status304 = False
- if req.method in self._safe_methods:
- if req.if_modified_since and self.last_modified and self.last_modified <= req.if_modified_since:
- status304 = True
- if req.if_none_match and self.etag:
- ## FIXME: should a weak match be okay?
- if self.etag in req.if_none_match:
- status304 = True
- else:
- # Even if If-Modified-Since matched, if ETag doesn't then reject it
- status304 = False
- if status304:
- start_response('304 Not Modified', self.headerlist)
- return []
- if req.method == 'HEAD':
- start_response(self.status, self.headerlist)
- return []
- if (req.range and req.if_range.match_response(self)
- and self.content_range is None
- and req.method == 'GET'
- and self.status_int == 200):
- content_range = req.range.content_range(self.content_length)
- if content_range is not None:
- app_iter = self.app_iter_range(content_range.start, content_range.stop)
- if app_iter is not None:
- headers = list(self.headerlist)
- headers.append(('Content-Range', str(content_range)))
- start_response('206 Partial Content', headers)
- return app_iter
- start_response(self.status, self.headerlist)
- return self.app_iter
-
- def app_iter_range(self, start, stop):
- """
- Return a new app_iter built from the response app_iter, that
- serves up only the given ``start:stop`` range.
- """
- if self._app_iter is None:
- return [self.body[start:stop]]
- app_iter = self.app_iter
- if hasattr(app_iter, 'app_iter_range'):
- return app_iter.app_iter_range(start, stop)
- return AppIterRange(app_iter, start, stop)
-
-
-Request.ResponseClass = Response
-Response.RequestClass = Request
-
-def _cgi_FieldStorage__repr__patch(self):
- """ monkey patch for FieldStorage.__repr__
-
- Unbelievely, the default __repr__ on FieldStorage reads
- the entire file content instead of being sane about it.
- This is a simple replacement that doesn't do that
- """
- if self.file:
- return "FieldStorage(%r, %r)" % (
- self.name, self.filename)
- return "FieldStorage(%r, %r, %r)" % (
- self.name, self.filename, self.value)
-
-cgi.FieldStorage.__repr__ = _cgi_FieldStorage__repr__patch
-
-class FakeCGIBody(object):
-
- def __init__(self, vars):
- self.vars = vars
- self._body = None
- self.position = 0
-
- def read(self, size=-1):
- body = self._get_body()
- if size == -1:
- v = body[self.position:]
- self.position = len(body)
- return v
- else:
- v = body[self.position:self.position+size]
- self.position = min(len(body), self.position+size)
- return v
-
- def _get_body(self):
- if self._body is None:
- self._body = urllib.urlencode(self.vars.items())
- return self._body
-
- def readline(self, size=None):
- # We ignore size, but allow it to be hinted
- rest = self._get_body()[self.position:]
- next = rest.find('\r\n')
- if next == -1:
- return self.read()
- self.position += next+2
- return rest[:next+2]
-
- def readlines(self, hint=None):
- # Again, allow hint but ignore
- body = self._get_body()
- rest = body[self.position:]
- self.position = len(body)
- result = []
- while 1:
- next = rest.find('\r\n')
- if next == -1:
- result.append(rest)
- break
- result.append(rest[:next+2])
- rest = rest[next+2:]
- return result
-
- def __iter__(self):
- return iter(self.readlines())
-
- def __repr__(self):
- inner = repr(self.vars)
- if len(inner) > 20:
- inner = inner[:15] + '...' + inner[-5:]
- return '<%s at %x viewing %s>' % (
- self.__class__.__name__,
- abs(id(self)), inner)
-
- #@classmethod
- def update_environ(cls, environ, vars):
- obj = cls(vars)
- environ['CONTENT_LENGTH'] = '-1'
- environ['wsgi.input'] = obj
-
- update_environ = classmethod(update_environ)
-
-class ResponseBodyFile(object):
-
- def __init__(self, response):
- self.response = response
-
- def __repr__(self):
- return '<body_file for %r>' % (
- self.response)
-
- def close(self):
- raise NotImplementedError(
- "Response bodies cannot be closed")
-
- def flush(self):
- pass
-
- def write(self, s):
- if isinstance(s, unicode):
- if self.response.charset is not None:
- s = s.encode(self.response.charset)
- else:
- raise TypeError(
- "You can only write unicode to Response.body_file "
- "if charset has been set")
- if not isinstance(s, str):
- raise TypeError(
- "You can only write str to a Response.body_file, not %s"
- % type(s))
- if not isinstance(self.response._app_iter, list):
- body = self.response.body
- if body:
- self.response.app_iter = [body]
- else:
- self.response.app_iter = []
- self.response.app_iter.append(s)
-
- def writelines(self, seq):
- for item in seq:
- self.write(item)
-
- closed = False
-
- def encoding(self):
- """
- The encoding of the file (inherited from response.charset)
- """
- return self.response.charset
-
- encoding = property(encoding, doc=encoding.__doc__)
-
- mode = 'wb'
-
-class AppIterRange(object):
- """
- Wraps an app_iter, returning just a range of bytes
- """
-
- def __init__(self, app_iter, start, stop):
- assert start >= 0, "Bad start: %r" % start
- assert stop is None or (stop >= 0 and stop >= start), (
- "Bad stop: %r" % stop)
- self.app_iter = app_iter
- self.app_iterator = iter(app_iter)
- self.start = start
- if stop is None:
- self.length = -1
- else:
- self.length = stop - start
- if start:
- self._served = None
- else:
- self._served = 0
- if hasattr(app_iter, 'close'):
- self.close = app_iter.close
-
- def __iter__(self):
- return self
-
- def next(self):
- if self._served is None:
- # Haven't served anything; need to skip some leading bytes
- skipped = 0
- start = self.start
- while 1:
- chunk = self.app_iterator.next()
- skipped += len(chunk)
- extra = skipped - start
- if extra == 0:
- self._served = 0
- break
- elif extra > 0:
- self._served = extra
- return chunk[-extra:]
- length = self.length
- if length is None:
- # Spent
- raise StopIteration
- chunk = self.app_iterator.next()
- if length == -1:
- return chunk
- if self._served + len(chunk) > length:
- extra = self._served + len(chunk) - length
- self.length = None
- return chunk[:-extra]
- self._served += len(chunk)
- return chunk
-
diff --git a/lib/webob/acceptparse.py b/lib/webob/acceptparse.py
@@ -1,297 +0,0 @@
-"""
-Parses a variety of ``Accept-*`` headers.
-
-These headers generally take the form of::
-
- value1; q=0.5, value2; q=0
-
-Where the ``q`` parameter is optional. In theory other parameters
-exists, but this ignores them.
-"""
-
-import re
-try:
- sorted
-except NameError:
- from webob.compat import sorted
-
-part_re = re.compile(
- r',\s*([^\s;,\n]+)(?:[^,]*?;\s*q=([0-9.]*))?')
-
-def parse_accept(value):
- """
- Parses an ``Accept-*`` style header.
-
- A list of ``[(value, quality), ...]`` is returned. ``quality``
- will be 1 if it was not given.
- """
- result = []
- for match in part_re.finditer(','+value):
- name = match.group(1)
- if name == 'q':
- continue
- quality = match.group(2) or ''
- if not quality:
- quality = 1
- else:
- try:
- quality = max(min(float(quality), 1), 0)
- except ValueError:
- quality = 1
- result.append((name, quality))
- return result
-
-class Accept(object):
- """
- Represents a generic ``Accept-*`` style header.
-
- This object should not be modified. To add items you can use
- ``accept_obj + 'accept_thing'`` to get a new object
- """
-
- def __init__(self, header_name, header_value):
- self.header_name = header_name
- self.header_value = header_value
- self._parsed = parse_accept(header_value)
-
- def __repr__(self):
- return '<%s at %x %s: %s>' % (
- self.__class__.__name__,
- abs(id(self)),
- self.header_name, str(self))
-
- def __str__(self):
- result = []
- for match, quality in self._parsed:
- if quality != 1:
- match = '%s;q=%0.1f' % (match, quality)
- result.append(match)
- return ', '.join(result)
-
- # FIXME: should subtraction be allowed?
- def __add__(self, other, reversed=False):
- if isinstance(other, Accept):
- other = other.header_value
- if hasattr(other, 'items'):
- other = sorted(other.items(), key=lambda item: -item[1])
- if isinstance(other, (list, tuple)):
- result = []
- for item in other:
- if isinstance(item, (list, tuple)):
- name, quality = item
- result.append('%s; q=%s' % (name, quality))
- else:
- result.append(item)
- other = ', '.join(result)
- other = str(other)
- my_value = self.header_value
- if reversed:
- other, my_value = my_value, other
- if not other:
- new_value = my_value
- elif not my_value:
- new_value = other
- else:
- new_value = my_value + ', ' + other
- return self.__class__(self.header_name, new_value)
-
- def __radd__(self, other):
- return self.__add__(other, True)
-
- def __contains__(self, match):
- """
- Returns true if the given object is listed in the accepted
- types.
- """
- for item, quality in self._parsed:
- if self._match(item, match):
- return True
-
- def quality(self, match):
- """
- Return the quality of the given match. Returns None if there
- is no match (not 0).
- """
- for item, quality in self._parsed:
- if self._match(item, match):
- return quality
- return None
-
- def first_match(self, matches):
- """
- Returns the first match in the sequences of matches that is
- allowed. Ignores quality. Returns the first item if nothing
- else matches; or if you include None at the end of the match
- list then that will be returned.
- """
- if not matches:
- raise ValueError(
- "You must pass in a non-empty list")
- for match in matches:
- for item, quality in self._parsed:
- if self._match(item, match):
- return match
- if match is None:
- return None
- return matches[0]
-
- def best_match(self, matches, default_match=None):
- """
- Returns the best match in the sequence of matches.
-
- The sequence can be a simple sequence, or you can have
- ``(match, server_quality)`` items in the sequence. If you
- have these tuples then the client quality is multiplied by the
- server_quality to get a total.
-
- default_match (default None) is returned if there is no intersection.
- """
- best_quality = -1
- best_match = default_match
- for match_item in matches:
- if isinstance(match_item, (tuple, list)):
- match, server_quality = match_item
- else:
- match = match_item
- server_quality = 1
- for item, quality in self._parsed:
- possible_quality = server_quality * quality
- if possible_quality < best_quality:
- continue
- if self._match(item, match):
- best_quality = possible_quality
- best_match = match
- return best_match
-
- def best_matches(self, fallback=None):
- """
- Return all the matches in order of quality, with fallback (if
- given) at the end.
- """
- items = [
- i for i, q in sorted(self._parsed, key=lambda iq: -iq[1])]
- if fallback:
- for index, item in enumerate(items):
- if self._match(item, fallback):
- items[index+1:] = []
- break
- else:
- items.append(fallback)
- return items
-
- def _match(self, item, match):
- return item.lower() == match.lower() or item == '*'
-
-class NilAccept(object):
-
- """
- Represents an Accept header with no value.
- """
-
- MasterClass = Accept
-
- def __init__(self, header_name):
- self.header_name = header_name
-
- def __repr__(self):
- return '<%s for %s: %s>' % (
- self.__class__.__name__, self.header_name, self.MasterClass)
-
- def __str__(self):
- return ''
-
- def __add__(self, item):
- if isinstance(item, self.MasterClass):
- return item
- else:
- return self.MasterClass(self.header_name, '') + item
-
- def __radd__(self, item):
- if isinstance(item, self.MasterClass):
- return item
- else:
- return item + self.MasterClass(self.header_name, '')
-
- def __contains__(self, item):
- return True
-
- def quality(self, match, default_quality=1):
- return 0
-
- def first_match(self, matches):
- return matches[0]
-
- def best_match(self, matches, default_match=None):
- best_quality = -1
- best_match = default_match
- for match_item in matches:
- if isinstance(match_item, (list, tuple)):
- match, quality = match_item
- else:
- match = match_item
- quality = 1
- if quality > best_quality:
- best_match = match
- best_quality = quality
- return best_match
-
- def best_matches(self, fallback=None):
- if fallback:
- return [fallback]
- else:
- return []
-
-class NoAccept(NilAccept):
-
- def __contains__(self, item):
- return False
-
-class MIMEAccept(Accept):
-
- """
- Represents the ``Accept`` header, which is a list of mimetypes.
-
- This class knows about mime wildcards, like ``image/*``
- """
-
- def _match(self, item, match):
- item = item.lower()
- if item == '*':
- item = '*/*'
- match = match.lower()
- if match == '*':
- match = '*/*'
- if '/' not in item:
- # Bad, but we ignore
- return False
- if '/' not in match:
- raise ValueError(
- "MIME matches must include / (bad: %r)" % match)
- item_major, item_minor = item.split('/', 1)
- match_major, match_minor = match.split('/', 1)
- if match_major == '*' and match_minor != '*':
- raise ValueError(
- "A MIME type of %r doesn't make sense" % match)
- if item_major == '*' and item_minor != '*':
- # Bad, but we ignore
- return False
- if ((item_major == '*' and item_minor == '*')
- or (match_major == '*' and match_minor == '*')):
- return True
- if (item_major == match_major
- and ((item_minor == '*' or match_minor == '*')
- or item_minor == match_minor)):
- return True
- return False
-
- def accept_html(self):
- """
- Returns true if any HTML-like type is accepted
- """
- return ('text/html' in self
- or 'application/xhtml+xml' in self
- or 'application/xml' in self
- or 'text/xml' in self)
-
-class MIMENilAccept(NilAccept):
- MasterClass = MIMEAccept
diff --git a/lib/webob/byterange.py b/lib/webob/byterange.py
@@ -1,295 +0,0 @@
-class Range(object):
-
- """
- Represents the Range header.
-
- This only represents ``bytes`` ranges, which are the only kind
- specified in HTTP. This can represent multiple sets of ranges,
- but no place else is this multi-range facility supported.
- """
-
- def __init__(self, ranges):
- for begin, end in ranges:
- assert end is None or end >= 0, "Bad ranges: %r" % ranges
- self.ranges = ranges
-
- def satisfiable(self, length):
- """
- Returns true if this range can be satisfied by the resource
- with the given byte length.
- """
- for begin, end in self.ranges:
- if end is not None and end >= length:
- return False
- return True
-
- def range_for_length(self, length):
- """
- *If* there is only one range, and *if* it is satisfiable by
- the given length, then return a (begin, end) non-inclusive range
- of bytes to serve. Otherwise return None
-
- If length is None (unknown length), then the resulting range
- may be (begin, None), meaning it should be served from that
- point. If it's a range with a fixed endpoint we won't know if
- it is satisfiable, so this will return None.
- """
- if len(self.ranges) != 1:
- return None
- begin, end = self.ranges[0]
- if length is None:
- # Unknown; only works with ranges with no end-point
- if end is None:
- return (begin, end)
- return None
- if end >= length:
- # Overshoots the end
- return None
- return (begin, end)
-
- def content_range(self, length):
- """
- Works like range_for_length; returns None or a ContentRange object
-
- You can use it like::
-
- response.content_range = req.range.content_range(response.content_length)
-
- Though it's still up to you to actually serve that content range!
- """
- range = self.range_for_length(length)
- if range is None:
- return None
- return ContentRange(range[0], range[1], length)
-
- def __str__(self):
- return self.serialize_bytes('bytes', self.python_ranges_to_bytes(self.ranges))
-
- def __repr__(self):
- return '<%s ranges=%s>' % (
- self.__class__.__name__,
- ', '.join(map(repr, self.ranges)))
-
- #@classmethod
- def parse(cls, header):
- """
- Parse the header; may return None if header is invalid
- """
- bytes = cls.parse_bytes(header)
- if bytes is None:
- return None
- units, ranges = bytes
- if units.lower() != 'bytes':
- return None
- ranges = cls.bytes_to_python_ranges(ranges)
- if ranges is None:
- return None
- return cls(ranges)
- parse = classmethod(parse)
-
- #@staticmethod
- def parse_bytes(header):
- """
- Parse a Range header into (bytes, list_of_ranges). Note that the
- ranges are *inclusive* (like in HTTP, not like in Python
- typically).
-
- Will return None if the header is invalid
- """
- if not header:
- raise TypeError(
- "The header must not be empty")
- ranges = []
- last_end = 0
- try:
- (units, range) = header.split("=", 1)
- units = units.strip().lower()
- for item in range.split(","):
- if '-' not in item:
- raise ValueError()
- if item.startswith('-'):
- # This is a range asking for a trailing chunk
- if last_end < 0:
- raise ValueError('too many end ranges')
- begin = int(item)
- end = None
- last_end = -1
- else:
- (begin, end) = item.split("-", 1)
- begin = int(begin)
- if begin < last_end or last_end < 0:
- print begin, last_end
- raise ValueError('begin<last_end, or last_end<0')
- if not end.strip():
- end = None
- else:
- end = int(end)
- if end is not None and begin > end:
- raise ValueError('begin>end')
- last_end = end
- ranges.append((begin, end))
- except ValueError, e:
- # In this case where the Range header is malformed,
- # section 14.16 says to treat the request as if the
- # Range header was not present. How do I log this?
- print e
- return None
- return (units, ranges)
- parse_bytes = staticmethod(parse_bytes)
-
- #@staticmethod
- def serialize_bytes(units, ranges):
- """
- Takes the output of parse_bytes and turns it into a header
- """
- parts = []
- for begin, end in ranges:
- if end is None:
- if begin >= 0:
- parts.append('%s-' % begin)
- else:
- parts.append(str(begin))
- else:
- if begin < 0:
- raise ValueError(
- "(%r, %r) should have a non-negative first value" % (begin, end))
- if end < 0:
- raise ValueError(
- "(%r, %r) should have a non-negative second value" % (begin, end))
- parts.append('%s-%s' % (begin, end))
- return '%s=%s' % (units, ','.join(parts))
- serialize_bytes = staticmethod(serialize_bytes)
-
- #@staticmethod
- def bytes_to_python_ranges(ranges, length=None):
- """
- Converts the list-of-ranges from parse_bytes() to a Python-style
- list of ranges (non-inclusive end points)
-
- In the list of ranges, the last item can be None to indicate that
- it should go to the end of the file, and the first item can be
- negative to indicate that it should start from an offset from the
- end. If you give a length then this will not occur (negative
- numbers and offsets will be resolved).
-
- If length is given, and any range is not value, then None is
- returned.
- """
- result = []
- for begin, end in ranges:
- if begin < 0:
- if length is None:
- result.append((begin, None))
- continue
- else:
- begin = length - begin
- end = length
- if begin is None:
- begin = 0
- if end is None and length is not None:
- end = length
- if length is not None and end is not None and end > length:
- return None
- if end is not None:
- end -= 1
- result.append((begin, end))
- return result
- bytes_to_python_ranges = staticmethod(bytes_to_python_ranges)
-
- #@staticmethod
- def python_ranges_to_bytes(ranges):
- """
- Converts a Python-style list of ranges to what serialize_bytes
- expects.
-
- This is the inverse of bytes_to_python_ranges
- """
- result = []
- for begin, end in ranges:
- if end is None:
- result.append((begin, None))
- else:
- result.append((begin, end+1))
- return result
- python_ranges_to_bytes = staticmethod(python_ranges_to_bytes)
-
-class ContentRange(object):
-
- """
- Represents the Content-Range header
-
- This header is ``start-stop/length``, where stop and length can be
- ``*`` (represented as None in the attributes).
- """
-
- def __init__(self, start, stop, length):
- assert start >= 0, "Bad start: %r" % start
- assert stop is None or (stop >= 0 and stop >= start), (
- "Bad stop: %r" % stop)
- self.start = start
- self.stop = stop
- self.length = length
-
- def __repr__(self):
- return '<%s %s>' % (
- self.__class__.__name__,
- self)
-
- def __str__(self):
- if self.stop is None:
- stop = '*'
- else:
- stop = self.stop + 1
- if self.length is None:
- length = '*'
- else:
- length = self.length
- return 'bytes %s-%s/%s' % (self.start, stop, length)
-
- def __iter__(self):
- """
- Mostly so you can unpack this, like:
-
- start, stop, length = res.content_range
- """
- return iter([self.start, self.stop, self.length])
-
- #@classmethod
- def parse(cls, value):
- """
- Parse the header. May return None if it cannot parse.
- """
- if value is None:
- return None
- value = value.strip()
- if not value.startswith('bytes '):
- # Unparseable
- return None
- value = value[len('bytes '):].strip()
- if '/' not in value:
- # Invalid, no length given
- return None
- range, length = value.split('/', 1)
- if '-' not in range:
- # Invalid, no range
- return None
- start, end = range.split('-', 1)
- try:
- start = int(start)
- if end == '*':
- end = None
- else:
- end = int(end)
- if length == '*':
- length = None
- else:
- length = int(length)
- except ValueError:
- # Parse problem
- return None
- if end is None:
- return cls(start, None, length)
- else:
- return cls(start, end-1, length)
- parse = classmethod(parse)
-
diff --git a/lib/webob/cachecontrol.py b/lib/webob/cachecontrol.py
@@ -1,169 +0,0 @@
-"""
-Represents the Cache-Control header
-"""
-
-import re
-from webob.updatedict import UpdateDict
-try:
- sorted
-except NameError:
- from webob.compat import sorted
-
-token_re = re.compile(
- r'([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?')
-need_quote_re = re.compile(r'[^a-zA-Z0-9._-]')
-
-class exists_property(object):
- """
- Represents a property that either is listed in the Cache-Control
- header, or is not listed (has no value)
- """
- def __init__(self, prop, type=None):
- self.prop = prop
- self.type = type
-
- def __get__(self, obj, type=None):
- if obj is None:
- return self
- return self.prop in obj.properties
- def __set__(self, obj, value):
- if (self.type is not None
- and self.type != obj.type):
- raise AttributeError(
- "The property %s only applies to %s Cache-Control" % (self.prop, self.type))
- if value:
- obj.properties[self.prop] = None
- else:
- if self.prop in obj.properties:
- del obj.properties[self.prop]
- def __delete__(self, obj):
- self.__set__(obj, False)
-
-class value_property(object):
- """
- Represents a property that has a value in the Cache-Control header.
-
- When no value is actually given, the value of self.none is returned.
- """
- def __init__(self, prop, default=None, none=None, type=None):
- self.prop = prop
- self.default = default
- self.none = none
- self.type = type
- def __get__(self, obj, type=None):
- if obj is None:
- return self
- if self.prop in obj.properties:
- value = obj.properties[self.prop]
- if value is None:
- return self.none
- else:
- return value
- else:
- return self.default
- def __set__(self, obj, value):
- if (self.type is not None
- and self.type != obj.type):
- raise AttributeError(
- "The property %s only applies to %s Cache-Control" % (self.prop, self.type))
- if value == self.default:
- if self.prop in obj.properties:
- del obj.properties[self.prop]
- elif value is True:
- obj.properties[self.prop] = None # Empty value, but present
- else:
- obj.properties[self.prop] = value
- def __delete__(self, obj):
- if self.prop in obj.properties:
- del obj.properties[self.prop]
-
-class CacheControl(object):
-
- """
- Represents the Cache-Control header.
-
- By giving a type of ``'request'`` or ``'response'`` you can
- control what attributes are allowed (some Cache-Control values
- only apply to requests or responses).
- """
-
- def __init__(self, properties, type):
- self.properties = properties
- self.type = type
-
- #@classmethod
- def parse(cls, header, updates_to=None, type=None):
- """
- Parse the header, returning a CacheControl object.
-
- The object is bound to the request or response object
- ``updates_to``, if that is given.
- """
- if updates_to:
- props = UpdateDict()
- props.updated = updates_to
- else:
- props = {}
- for match in token_re.finditer(header):
- name = match.group(1)
- value = match.group(2) or match.group(3) or None
- if value:
- try:
- value = int(value)
- except ValueError:
- pass
- props[name] = value
- obj = cls(props, type=type)
- if updates_to:
- props.updated_args = (obj,)
- return obj
-
- parse = classmethod(parse)
-
- def __repr__(self):
- return '<CacheControl %r>' % str(self)
-
- # Request values:
- # no-cache shared (below)
- # no-store shared (below)
- # max-age shared (below)
- max_stale = value_property('max-stale', none='*', type='request')
- min_fresh = value_property('min-fresh', type='request')
- # no-transform shared (below)
- only_if_cached = exists_property('only-if-cached', type='request')
-
- # Response values:
- public = exists_property('public', type='response')
- private = value_property('private', none='*', type='response')
- no_cache = value_property('no-cache', none='*')
- no_store = exists_property('no-store')
- no_transform = exists_property('no-transform')
- must_revalidate = exists_property('must-revalidate', type='response')
- proxy_revalidate = exists_property('proxy-revalidate', type='response')
- max_age = value_property('max-age', none=-1)
- s_maxage = value_property('s-maxage', type='response')
- s_max_age = s_maxage
-
- def __str__(self):
- return serialize_cache_control(self.properties)
-
- def copy(self):
- """
- Returns a copy of this object.
- """
- return self.__class__(self.properties.copy(), type=self.type)
-
-def serialize_cache_control(properties):
- if isinstance(properties, CacheControl):
- properties = properties.properties
- parts = []
- for name, value in sorted(properties.items()):
- if value is None:
- parts.append(name)
- continue
- value = str(value)
- if need_quote_re.search(value):
- value = '"%s"' % value
- parts.append('%s=%s' % (name, value))
- return ', '.join(parts)
-
diff --git a/lib/webob/compat.py b/lib/webob/compat.py
@@ -1,23 +0,0 @@
-try:
- # This will succeed on Python 2.4, and fail on Python 2.3.
-
- [].sort(key=lambda: None)
-
- def sorted(iterable, cmp=None, key=None, reverse=False):
- l = list(iterable)
- l.sort(cmp=cmp, key=key, reverse=reverse)
- return l
-
-except TypeError:
- # Implementation for Python 2.3.
-
- def sorted(iterable, key=None, reverse=False):
- l = list(iterable)
- if key:
- l = [(key(i), i) for i in l]
- l.sort()
- if key:
- l = [i[1] for i in l]
- if reverse:
- l.reverse()
- return l
diff --git a/lib/webob/datastruct.py b/lib/webob/datastruct.py
@@ -1,58 +0,0 @@
-"""
-Contains some data structures.
-"""
-
-from webob.util.dictmixin import DictMixin
-
-class EnvironHeaders(DictMixin):
- """An object that represents the headers as present in a
- WSGI environment.
-
- This object is a wrapper (with no internal state) for a WSGI
- request object, representing the CGI-style HTTP_* keys as a
- dictionary. Because a CGI environment can only hold one value for
- each key, this dictionary is single-valued (unlike outgoing
- headers).
- """
-
- def __init__(self, environ):
- self.environ = environ
-
- def _trans_name(self, name):
- key = 'HTTP_'+name.replace('-', '_').upper()
- if key == 'HTTP_CONTENT_LENGTH':
- key = 'CONTENT_LENGTH'
- elif key == 'HTTP_CONTENT_TYPE':
- key = 'CONTENT_TYPE'
- return key
-
- def _trans_key(self, key):
- if key == 'CONTENT_TYPE':
- return 'Content-Type'
- elif key == 'CONTENT_LENGTH':
- return 'Content-Length'
- elif key.startswith('HTTP_'):
- return key[5:].replace('_', '-').title()
- else:
- return None
-
- def __getitem__(self, item):
- return self.environ[self._trans_name(item)]
-
- def __setitem__(self, item, value):
- self.environ[self._trans_name(item)] = value
-
- def __delitem__(self, item):
- del self.environ[self._trans_name(item)]
-
- def __iter__(self):
- for key in self.environ:
- name = self._trans_key(key)
- if name is not None:
- yield name
-
- def keys(self):
- return list(iter(self))
-
- def __contains__(self, item):
- return self._trans_name(item) in self.environ
diff --git a/lib/webob/etag.py b/lib/webob/etag.py
@@ -1,214 +0,0 @@
-"""
-Does parsing of ETag-related headers: If-None-Matches, If-Matches
-
-Also If-Range parsing
-"""
-
-import webob
-
-__all__ = ['AnyETag', 'NoETag', 'ETagMatcher', 'IfRange', 'NoIfRange']
-
-class _AnyETag(object):
- """
- Represents an ETag of *, or a missing ETag when matching is 'safe'
- """
-
- def __repr__(self):
- return '<ETag *>'
-
- def __nonzero__(self):
- return False
-
- def __contains__(self, other):
- return True
-
- def weak_match(self, other):
- return True
-
- def __str__(self):
- return '*'
-
-AnyETag = _AnyETag()
-
-class _NoETag(object):
- """
- Represents a missing ETag when matching is unsafe
- """
-
- def __repr__(self):
- return '<No ETag>'
-
- def __nonzero__(self):
- return False
-
- def __contains__(self, other):
- return False
-
- def weak_match(self, other):
- return False
-
- def __str__(self):
- return ''
-
-NoETag = _NoETag()
-
-class ETagMatcher(object):
-
- """
- Represents an ETag request. Supports containment to see if an
- ETag matches. You can also use
- ``etag_matcher.weak_contains(etag)`` to allow weak ETags to match
- (allowable for conditional GET requests, but not ranges or other
- methods).
- """
-
- def __init__(self, etags, weak_etags=()):
- self.etags = etags
- self.weak_etags = weak_etags
-
- def __contains__(self, other):
- return other in self.etags
-
- def weak_match(self, other):
- if other.lower().startswith('w/'):
- other = other[2:]
- return other in self.etags or other in self.weak_etags
-
- def __repr__(self):
- return '<ETag %s>' % (
- ' or '.join(self.etags))
-
- def parse(cls, value):
- """
- Parse this from a header value
- """
- results = []
- weak_results = []
- while value:
- if value.lower().startswith('w/'):
- # Next item is weak
- weak = True
- value = value[2:]
- else:
- weak = False
- if value.startswith('"'):
- try:
- etag, rest = value[1:].split('"', 1)
- except ValueError:
- etag = value.strip(' ",')
- rest = ''
- else:
- rest = rest.strip(', ')
- else:
- if ',' in value:
- etag, rest = value.split(',', 1)
- rest = rest.strip()
- else:
- etag = value
- rest = ''
- if etag == '*':
- return AnyETag
- if etag:
- if weak:
- weak_results.append(etag)
- else:
- results.append(etag)
- value = rest
- return cls(results, weak_results)
- parse = classmethod(parse)
-
- def __str__(self):
- # FIXME: should I quote these?
- items = list(self.etags)
- for weak in self.weak_etags:
- items.append('W/%s' % weak)
- return ', '.join(items)
-
-class IfRange(object):
- """
- Parses and represents the If-Range header, which can be
- an ETag *or* a date
- """
- def __init__(self, etag=None, date=None):
- self.etag = etag
- self.date = date
-
- def __repr__(self):
- if self.etag is None:
- etag = '*'
- else:
- etag = str(self.etag)
- if self.date is None:
- date = '*'
- else:
- date = webob._serialize_date(self.date)
- return '<%s etag=%s, date=%s>' % (
- self.__class__.__name__,
- etag, date)
-
- def __str__(self):
- if self.etag is not None:
- return str(self.etag)
- elif self.date:
- return webob._serialize_date(self.date)
- else:
- return ''
-
- def match(self, etag=None, last_modified=None):
- """
- Return True if the If-Range header matches the given etag or last_modified
- """
- if self.date is not None:
- if last_modified is None:
- # Conditional with nothing to base the condition won't work
- return False
- return last_modified <= self.date
- elif self.etag is not None:
- if not etag:
- return False
- return etag in self.etag
- return True
-
- def match_response(self, response):
- """
- Return True if this matches the given ``webob.Response`` instance.
- """
- return self.match(etag=response.etag, last_modified=response.last_modified)
-
- #@classmethod
- def parse(cls, value):
- """
- Parse this from a header value.
- """
- date = etag = None
- if not value:
- etag = NoETag()
- elif value and value.endswith(' GMT'):
- # Must be a date
- date = webob._parse_date(value)
- else:
- etag = ETagMatcher.parse(value)
- return cls(etag=etag, date=date)
- parse = classmethod(parse)
-
-class _NoIfRange(object):
- """
- Represents a missing If-Range header
- """
-
- def __repr__(self):
- return '<Empty If-Range>'
-
- def __str__(self):
- return ''
-
- def __nonzero__(self):
- return False
-
- def match(self, etag=None, last_modified=None):
- return True
-
- def match_response(self, response):
- return True
-
-NoIfRange = _NoIfRange()
diff --git a/lib/webob/exc.py b/lib/webob/exc.py
@@ -1,660 +0,0 @@
-"""
-HTTP Exception
-
-This module processes Python exceptions that relate to HTTP exceptions
-by defining a set of exceptions, all subclasses of HTTPException.
-Each exception, in addition to being a Python exception that can be
-raised and caught, is also a WSGI application and ``webob.Response``
-object.
-
-This module defines exceptions according to RFC 2068 [1]_ : codes with
-100-300 are not really errors; 400's are client errors, and 500's are
-server errors. According to the WSGI specification [2]_ , the application
-can call ``start_response`` more then once only under two conditions:
-(a) the response has not yet been sent, or (b) if the second and
-subsequent invocations of ``start_response`` have a valid ``exc_info``
-argument obtained from ``sys.exc_info()``. The WSGI specification then
-requires the server or gateway to handle the case where content has been
-sent and then an exception was encountered.
-
-Exception
- HTTPException
- HTTPOk
- * 200 - HTTPOk
- * 201 - HTTPCreated
- * 202 - HTTPAccepted
- * 203 - HTTPNonAuthoritativeInformation
- * 204 - HTTPNoContent
- * 205 - HTTPResetContent
- * 206 - HTTPPartialContent
- HTTPRedirection
- * 300 - HTTPMultipleChoices
- * 301 - HTTPMovedPermanently
- * 302 - HTTPFound
- * 303 - HTTPSeeOther
- * 304 - HTTPNotModified
- * 305 - HTTPUseProxy
- * 306 - Unused (not implemented, obviously)
- * 307 - HTTPTemporaryRedirect
- HTTPError
- HTTPClientError
- * 400 - HTTPBadRequest
- * 401 - HTTPUnauthorized
- * 402 - HTTPPaymentRequired
- * 403 - HTTPForbidden
- * 404 - HTTPNotFound
- * 405 - HTTPMethodNotAllowed
- * 406 - HTTPNotAcceptable
- * 407 - HTTPProxyAuthenticationRequired
- * 408 - HTTPRequestTimeout
- * 409 - HTTPConfict
- * 410 - HTTPGone
- * 411 - HTTPLengthRequired
- * 412 - HTTPPreconditionFailed
- * 413 - HTTPRequestEntityTooLarge
- * 414 - HTTPRequestURITooLong
- * 415 - HTTPUnsupportedMediaType
- * 416 - HTTPRequestRangeNotSatisfiable
- * 417 - HTTPExpectationFailed
- HTTPServerError
- * 500 - HTTPInternalServerError
- * 501 - HTTPNotImplemented
- * 502 - HTTPBadGateway
- * 503 - HTTPServiceUnavailable
- * 504 - HTTPGatewayTimeout
- * 505 - HTTPVersionNotSupported
-
-References:
-
-.. [1] http://www.python.org/peps/pep-0333.html#error-handling
-.. [2] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5
-
-
-"""
-
-import re
-import urlparse
-import sys
-try:
- from string import Template
-except ImportError:
- from webob.util.stringtemplate import Template
-import types
-from webob import Response, Request, html_escape
-
-newstyle_exceptions = issubclass(Exception, object)
-
-tag_re = re.compile(r'<.*?>', re.S)
-br_re = re.compile(r'<br.*?>', re.I|re.S)
-comment_re = re.compile(r'<!--|-->')
-
-def no_escape(value):
- if value is None:
- return ''
- if not isinstance(value, basestring):
- if hasattr(value, '__unicode__'):
- value = unicode(value)
- else:
- value = str(value)
- return value
-
-def strip_tags(value):
- value = value.replace('\n', ' ')
- value = value.replace('\r', '')
- value = br_re.sub('\n', value)
- value = comment_re.sub('', value)
- value = tag_re.sub('', value)
- return value
-
-class HTTPException(Exception):
- """
- Exception used on pre-Python-2.5, where new-style classes cannot be used as
- an exception.
- """
-
- def __init__(self, message, wsgi_response):
- Exception.__init__(self, message)
- self.__dict__['wsgi_response'] = wsgi_response
-
- def __call__(self, environ, start_response):
- return self.wsgi_response(environ, start_response)
-
- def exception(self):
- return self
-
- exception = property(exception)
-
- # for old style exceptions
- if not newstyle_exceptions:
- def __getattr__(self, attr):
- if not attr.startswith('_'):
- return getattr(self.wsgi_response, attr)
- else:
- raise AttributeError(attr)
-
- def __setattr__(self, attr, value):
- if attr.startswith('_') or attr in ('args',):
- self.__dict__[attr] = value
- else:
- setattr(self.wsgi_response, attr, value)
-
-class WSGIHTTPException(Response, HTTPException):
-
- ## You should set in subclasses:
- # code = 200
- # title = 'OK'
- # explanation = 'why this happens'
- # body_template_obj = Template('response template')
- code = None
- title = None
- explanation = ''
- body_template_obj = Template('''\
-${explanation}<br /><br />
-${detail}
-${html_comment}
-''')
-
- plain_template_obj = Template('''\
-${status}
-
-${body}''')
-
- html_template_obj = Template('''\
-<html>
- <head>
- <title>${status}</title>
- </head>
- <body>
- <h1>${status}</h1>
- ${body}
- </body>
-</html>''')
-
- ## Set this to True for responses that should have no request body
- empty_body = False
-
- def __init__(self, detail=None, headers=None, comment=None,
- body_template=None):
- Response.__init__(self,
- status='%s %s' % (self.code, self.title),
- content_type='text/html')
- Exception.__init__(self, detail)
- if headers:
- self.headers.update(headers)
- self.detail = detail
- self.comment = comment
- if body_template is not None:
- self.body_template = body_template
- self.body_template_obj = Template(body_template)
- if self.empty_body:
- del self.content_type
- del self.content_length
-
- def _make_body(self, environ, escape):
- args = {
- 'explanation': escape(self.explanation),
- 'detail': escape(self.detail or ''),
- 'comment': escape(self.comment or ''),
- }
- if self.comment:
- args['html_comment'] = '<!-- %s -->' % escape(self.comment)
- else:
- args['html_comment'] = ''
- body_tmpl = self.body_template_obj
- if WSGIHTTPException.body_template_obj is not self.body_template_obj:
- # Custom template; add headers to args
- for k, v in environ.items():
- args[k] = escape(v)
- for k, v in self.headers.items():
- args[k.lower()] = escape(v)
- t_obj = self.body_template_obj
- return t_obj.substitute(args)
-
- def plain_body(self, environ):
- body = self._make_body(environ, no_escape)
- body = strip_tags(body)
- return self.plain_template_obj.substitute(status=self.status,
- title=self.title,
- body=body)
-
- def html_body(self, environ):
- body = self._make_body(environ, html_escape)
- return self.html_template_obj.substitute(status=self.status,
- body=body)
-
- def generate_response(self, environ, start_response):
- if self.content_length is not None:
- del self.content_length
- headerlist = list(self.headerlist)
- accept = environ.get('HTTP_ACCEPT', '')
- if accept and 'html' in accept or '*/*' in accept:
- body = self.html_body(environ)
- if not self.content_type:
- headerlist.append('text/html; charset=utf8')
- else:
- body = self.plain_body(environ)
- if not self.content_type:
- headerlist.append('text/plain; charset=utf8')
- headerlist.append(('Content-Length', str(len(body))))
- start_response(self.status, headerlist)
- return [body]
-
- def __call__(self, environ, start_response):
- if environ['REQUEST_METHOD'] == 'HEAD':
- start_response(self.status, self.headerlist)
- return []
- if not self.body and not self.empty_body:
- return self.generate_response(environ, start_response)
- return Response.__call__(self, environ, start_response)
-
- def wsgi_response(self):
- return self
-
- wsgi_response = property(wsgi_response)
-
- def exception(self):
- if newstyle_exceptions:
- return self
- else:
- return HTTPException(self.detail, self)
-
- exception = property(exception)
-
-class HTTPError(WSGIHTTPException):
- """
- base class for status codes in the 400's and 500's
-
- This is an exception which indicates that an error has occurred,
- and that any work in progress should not be committed. These are
- typically results in the 400's and 500's.
- """
-
-class HTTPRedirection(WSGIHTTPException):
- """
- base class for 300's status code (redirections)
-
- This is an abstract base class for 3xx redirection. It indicates
- that further action needs to be taken by the user agent in order
- to fulfill the request. It does not necessarly signal an error
- condition.
- """
-
-class HTTPOk(WSGIHTTPException):
- """
- Base class for the 200's status code (successful responses)
- """
- code = 200
- title = 'OK'
-
-############################################################
-## 2xx success
-############################################################
-
-class HTTPCreated(HTTPOk):
- code = 201
- title = 'Created'
-
-class HTTPAccepted(HTTPOk):
- code = 202
- title = 'Accepted'
- explanation = 'The request is accepted for processing.'
-
-class HTTPNonAuthoritativeInformation(HTTPOk):
- code = 203
- title = 'Non-Authoritative Information'
-
-class HTTPNoContent(HTTPOk):
- code = 204
- title = 'No Content'
- empty_body = True
-
-class HTTPResetContent(HTTPOk):
- code = 205
- title = 'Reset Content'
- empty_body = True
-
-class HTTPPartialContent(HTTPOk):
- code = 206
- title = 'Partial Content'
-
-## FIXME: add 207 Multi-Status (but it's complicated)
-
-############################################################
-## 3xx redirection
-############################################################
-
-class _HTTPMove(HTTPRedirection):
- """
- redirections which require a Location field
-
- Since a 'Location' header is a required attribute of 301, 302, 303,
- 305 and 307 (but not 304), this base class provides the mechanics to
- make this easy.
-
- You can provide a location keyword argument to set the location
- immediately. You may also give ``add_slash=True`` if you want to
- redirect to the same URL as the request, except with a ``/`` added
- to the end.
-
- Relative URLs in the location will be resolved to absolute.
- """
- explanation = 'The resource has been moved to'
- body_template_obj = Template('''\
-${explanation} <a href="${location}">${location}</a>;
-you should be redirected automatically.
-${detail}
-${html_comment}''')
-
- def __init__(self, detail=None, headers=None, comment=None,
- body_template=None, location=None, add_slash=False):
- super(_HTTPMove, self).__init__(
- detail=detail, headers=headers, comment=comment,
- body_template=body_template)
- if location is not None:
- self.location = location
- if add_slash:
- raise TypeError(
- "You can only provide one of the arguments location and add_slash")
- self.add_slash = add_slash
-
- def __call__(self, environ, start_response):
- req = Request(environ)
- if self.add_slash:
- url = req.path_url
- url += '/'
- if req.environ.get('QUERY_STRING'):
- url += '?' + req.environ['QUERY_STRING']
- self.location = url
- self.location = urlparse.urljoin(req.path_url, self.location)
- return super(_HTTPMove, self).__call__(
- environ, start_response)
-
-class HTTPMultipleChoices(_HTTPMove):
- code = 300
- title = 'Multiple Choices'
-
-class HTTPMovedPermanently(_HTTPMove):
- code = 301
- title = 'Moved Permanently'
-
-class HTTPFound(_HTTPMove):
- code = 302
- title = 'Found'
- explanation = 'The resource was found at'
-
-# This one is safe after a POST (the redirected location will be
-# retrieved with GET):
-class HTTPSeeOther(_HTTPMove):
- code = 303
- title = 'See Other'
-
-class HTTPNotModified(HTTPRedirection):
- # FIXME: this should include a date or etag header
- code = 304
- title = 'Not Modified'
- empty_body = True
-
-class HTTPUseProxy(_HTTPMove):
- # Not a move, but looks a little like one
- code = 305
- title = 'Use Proxy'
- explanation = (
- 'The resource must be accessed through a proxy located at')
-
-class HTTPTemporaryRedirect(_HTTPMove):
- code = 307
- title = 'Temporary Redirect'
-
-############################################################
-## 4xx client error
-############################################################
-
-class HTTPClientError(HTTPError):
- """
- base class for the 400's, where the client is in error
-
- This is an error condition in which the client is presumed to be
- in-error. This is an expected problem, and thus is not considered
- a bug. A server-side traceback is not warranted. Unless specialized,
- this is a '400 Bad Request'
- """
- code = 400
- title = 'Bad Request'
- explanation = ('The server could not comply with the request since\r\n'
- 'it is either malformed or otherwise incorrect.\r\n')
-
-class HTTPBadRequest(HTTPClientError):
- pass
-
-class HTTPUnauthorized(HTTPClientError):
- code = 401
- title = 'Unauthorized'
- explanation = (
- 'This server could not verify that you are authorized to\r\n'
- 'access the document you requested. Either you supplied the\r\n'
- 'wrong credentials (e.g., bad password), or your browser\r\n'
- 'does not understand how to supply the credentials required.\r\n')
-
-class HTTPPaymentRequired(HTTPClientError):
- code = 402
- title = 'Payment Required'
- explanation = ('Access was denied for financial reasons.')
-
-class HTTPForbidden(HTTPClientError):
- code = 403
- title = 'Forbidden'
- explanation = ('Access was denied to this resource.')
-
-class HTTPNotFound(HTTPClientError):
- code = 404
- title = 'Not Found'
- explanation = ('The resource could not be found.')
-
-class HTTPMethodNotAllowed(HTTPClientError):
- code = 405
- title = 'Method Not Allowed'
- # override template since we need an environment variable
- body_template_obj = Template('''\
-The method ${REQUEST_METHOD} is not allowed for this resource. <br /><br />
-${detail}''')
-
-class HTTPNotAcceptable(HTTPClientError):
- code = 406
- title = 'Not Acceptable'
- # override template since we need an environment variable
- template = Template('''\
-The resource could not be generated that was acceptable to your browser
-(content of type ${HTTP_ACCEPT}. <br /><br />
-${detail}''')
-
-class HTTPProxyAuthenticationRequired(HTTPClientError):
- code = 407
- title = 'Proxy Authentication Required'
- explanation = ('Authentication with a local proxy is needed.')
-
-class HTTPRequestTimeout(HTTPClientError):
- code = 408
- title = 'Request Timeout'
- explanation = ('The server has waited too long for the request to '
- 'be sent by the client.')
-
-class HTTPConflict(HTTPClientError):
- code = 409
- title = 'Conflict'
- explanation = ('There was a conflict when trying to complete '
- 'your request.')
-
-class HTTPGone(HTTPClientError):
- code = 410
- title = 'Gone'
- explanation = ('This resource is no longer available. No forwarding '
- 'address is given.')
-
-class HTTPLengthRequired(HTTPClientError):
- code = 411
- title = 'Length Required'
- explanation = ('Content-Length header required.')
-
-class HTTPPreconditionFailed(HTTPClientError):
- code = 412
- title = 'Precondition Failed'
- explanation = ('Request precondition failed.')
-
-class HTTPRequestEntityTooLarge(HTTPClientError):
- code = 413
- title = 'Request Entity Too Large'
- explanation = ('The body of your request was too large for this server.')
-
-class HTTPRequestURITooLong(HTTPClientError):
- code = 414
- title = 'Request-URI Too Long'
- explanation = ('The request URI was too long for this server.')
-
-class HTTPUnsupportedMediaType(HTTPClientError):
- code = 415
- title = 'Unsupported Media Type'
- # override template since we need an environment variable
- template_obj = Template('''\
-The request media type ${CONTENT_TYPE} is not supported by this server.
-<br /><br />
-${detail}''')
-
-class HTTPRequestRangeNotSatisfiable(HTTPClientError):
- code = 416
- title = 'Request Range Not Satisfiable'
- explanation = ('The Range requested is not available.')
-
-class HTTPExpectationFailed(HTTPClientError):
- code = 417
- title = 'Expectation Failed'
- explanation = ('Expectation failed.')
-
-class HTTPUnprocessableEntity(HTTPClientError):
- ## Note: from WebDAV
- code = 422
- title = 'Unprocessable Entity'
- explanation = 'Unable to process the contained instructions'
-
-class HTTPLocked(HTTPClientError):
- ## Note: from WebDAV
- code = 423
- title = 'Locked'
- explanation = ('The resource is locked')
-
-class HTTPFailedDependency(HTTPClientError):
- ## Note: from WebDAV
- code = 424
- title = 'Failed Dependency'
- explanation = ('The method could not be performed because the requested '
- 'action dependended on another action and that action failed')
-
-############################################################
-## 5xx Server Error
-############################################################
-# Response status codes beginning with the digit "5" indicate cases in
-# which the server is aware that it has erred or is incapable of
-# performing the request. Except when responding to a HEAD request, the
-# server SHOULD include an entity containing an explanation of the error
-# situation, and whether it is a temporary or permanent condition. User
-# agents SHOULD display any included entity to the user. These response
-# codes are applicable to any request method.
-
-class HTTPServerError(HTTPError):
- """
- base class for the 500's, where the server is in-error
-
- This is an error condition in which the server is presumed to be
- in-error. This is usually unexpected, and thus requires a traceback;
- ideally, opening a support ticket for the customer. Unless specialized,
- this is a '500 Internal Server Error'
- """
- code = 500
- title = 'Internal Server Error'
- explanation = (
- 'The server has either erred or is incapable of performing\r\n'
- 'the requested operation.\r\n')
-
-class HTTPInternalServerError(HTTPServerError):
- pass
-
-class HTTPNotImplemented(HTTPServerError):
- code = 501
- title = 'Not Implemented'
- template = Template('''
-The request method ${REQUEST_METHOD} is not implemented for this server. <br /><br />
-${detail}''')
-
-class HTTPBadGateway(HTTPServerError):
- code = 502
- title = 'Bad Gateway'
- explanation = ('Bad gateway.')
-
-class HTTPServiceUnavailable(HTTPServerError):
- code = 503
- title = 'Service Unavailable'
- explanation = ('The server is currently unavailable. '
- 'Please try again at a later time.')
-
-class HTTPGatewayTimeout(HTTPServerError):
- code = 504
- title = 'Gateway Timeout'
- explanation = ('The gateway has timed out.')
-
-class HTTPVersionNotSupported(HTTPServerError):
- code = 505
- title = 'HTTP Version Not Supported'
- explanation = ('The HTTP version is not supported.')
-
-class HTTPInsufficientStorage(HTTPServerError):
- code = 507
- title = 'Insufficient Storage'
- explanation = ('There was not enough space to save the resource')
-
-class HTTPExceptionMiddleware(object):
- """
- Middleware that catches exceptions in the sub-application. This
- does not catch exceptions in the app_iter; only during the initial
- calling of the application.
-
- This should be put *very close* to applications that might raise
- these exceptions. This should not be applied globally; letting
- *expected* exceptions raise through the WSGI stack is dangerous.
- """
-
- def __init__(self, application):
- self.application = application
- def __call__(self, environ, start_response):
- try:
- return self.application(environ, start_response)
- except HTTPException, exc:
- parent_exc_info = sys.exc_info()
- def repl_start_response(status, headers, exc_info=None):
- if exc_info is None:
- exc_info = parent_exc_info
- return start_response(status, headers, exc_info)
- return exc(environ, repl_start_response)
-
-try:
- from paste import httpexceptions
-except ImportError:
- # Without Paste we don't need to do this fixup
- pass
-else:
- for name in dir(httpexceptions):
- obj = globals().get(name)
- if (obj and isinstance(obj, type) and issubclass(obj, HTTPException)
- and obj is not HTTPException
- and obj is not WSGIHTTPException):
- obj.__bases__ = obj.__bases__ + (getattr(httpexceptions, name),)
- del name, obj, httpexceptions
-
-__all__ = ['HTTPExceptionMiddleware', 'status_map']
-status_map={}
-for name, value in globals().items():
- if (isinstance(value, (type, types.ClassType)) and issubclass(value, HTTPException)
- and not name.startswith('_')):
- __all__.append(name)
- if getattr(value, 'code', None):
- status_map[value.code]=value
-del name, value
-
diff --git a/lib/webob/headerdict.py b/lib/webob/headerdict.py
@@ -1,110 +0,0 @@
-"""
-Represents the response header list as a dictionary-like object.
-"""
-
-from webob.multidict import MultiDict
-try:
- reversed
-except NameError:
- from webob.util.reversed import reversed
-
-class HeaderDict(MultiDict):
-
- """
- Like a MultiDict, this wraps a list. Keys are normalized
- for case and whitespace.
- """
-
- def normalize(self, key):
- return str(key).lower().strip()
-
- def __getitem__(self, key):
- normalize = self.normalize
- key = normalize(key)
- for k, v in reversed(self._items):
- if normalize(k) == key:
- return v
- raise KeyError(key)
-
- def getall(self, key):
- normalize = self.normalize
- key = normalize(key)
- result = []
- for k, v in self._items:
- if normalize(k) == key:
- result.append(v)
- return result
-
- def mixed(self):
- result = {}
- multi = {}
- normalize = self.normalize
- for key, value in self.iteritems():
- key = normalize(key)
- if key in result:
- if key in multi:
- result[key].append(value)
- else:
- result[key] = [result[key], value]
- multi[key] = None
- else:
- result[key] = value
- return result
-
- def dict_of_lists(self):
- result = {}
- normalize = self.normalize
- for key, value in self.iteritems():
- key = normalize(key)
- if key in result:
- result[key].append(value)
- else:
- result[key] = [value]
- return result
-
- def __delitem__(self, key):
- normalize = self.normalize
- key = normalize(key)
- items = self._items
- found = False
- for i in range(len(items)-1, -1, -1):
- if normalize(items[i][0]) == key:
- del items[i]
- found = True
- if not found:
- raise KeyError(key)
-
- def __contains__(self, key):
- normalize = self.normalize
- key = normalize(key)
- for k, v in self._items:
- if normalize(k) == key:
- return True
- return False
-
- has_key = __contains__
-
- def setdefault(self, key, default=None):
- normalize = self.normalize
- c_key = normalize(key)
- for k, v in self._items:
- if normalize(k) == c_key:
- return v
- self._items.append((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))
- key = self.normalize(key)
- for i in range(len(self._items)):
- if self.normalize(self._items[i][0]) == key:
- v = self._items[i][1]
- del self._items[i]
- return v
- if args:
- return args[0]
- else:
- raise KeyError(key)
-
diff --git a/lib/webob/multidict.py b/lib/webob/multidict.py
@@ -1,606 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-"""
-Gives a multi-value dictionary object (MultiDict) plus several wrappers
-"""
-import cgi
-import copy
-import sys
-from webob.util.dictmixin import DictMixin
-try:
- reversed
-except NameError:
- from webob.util.reversed import reversed
-
-__all__ = ['MultiDict', 'UnicodeMultiDict', 'NestedMultiDict', 'NoVars']
-
-class MultiDict(DictMixin):
-
- """
- An ordered dictionary that can have multiple values for each key.
- Adds the methods getall, getone, mixed, and add to the normal
- dictionary interface.
- """
-
- def __init__(self, *args, **kw):
- if len(args) > 1:
- raise TypeError(
- "MultiDict can only be called with one positional argument")
- if args:
- if hasattr(args[0], 'iteritems'):
- items = list(args[0].iteritems())
- elif hasattr(args[0], 'items'):
- items = args[0].items()
- else:
- items = list(args[0])
- self._items = items
- else:
- self._items = []
- self._items.extend(kw.iteritems())
-
- #@classmethod
- def view_list(cls, lst):
- """
- Create a dict that is a view on the given list
- """
- if not isinstance(lst, list):
- raise TypeError(
- "%s.view_list(obj) takes only actual list objects, not %r"
- % (cls.__name__, lst))
- obj = cls()
- obj._items = lst
- return obj
-
- view_list = classmethod(view_list)
-
- #@classmethod
- def from_fieldstorage(cls, fs):
- """
- Create a dict from a cgi.FieldStorage instance
- """
- obj = cls()
- if fs.list:
- # fs.list can be None when there's nothing to parse
- for field in fs.list:
- if field.filename:
- obj.add(field.name, field)
- else:
- obj.add(field.name, field.value)
- return obj
-
- from_fieldstorage = classmethod(from_fieldstorage)
-
- def __getitem__(self, key):
- for k, v in reversed(self._items):
- if k == key:
- return v
- raise KeyError(key)
-
- def __setitem__(self, key, value):
- try:
- del self[key]
- except KeyError:
- pass
- self._items.append((key, value))
-
- def add(self, key, value):
- """
- Add the key and value, not overwriting any previous value.
- """
- self._items.append((key, value))
-
- def getall(self, key):
- """
- Return a list of all values matching the key (may be an empty list)
- """
- result = []
- for k, v in self._items:
- if key == k:
- result.append(v)
- return result
-
- def getone(self, key):
- """
- Get one value matching the key, raising a KeyError if multiple
- values were found.
- """
- v = self.getall(key)
- if not v:
- raise KeyError('Key not found: %r' % key)
- if len(v) > 1:
- raise KeyError('Multiple values match %r: %r' % (key, v))
- return v[0]
-
- def mixed(self):
- """
- Returns a dictionary where the values are either single
- values, or a list of values when a key/value appears more than
- once in this dictionary. This is similar to the kind of
- dictionary often used to represent the variables in a web
- request.
- """
- result = {}
- multi = {}
- for key, value in self.iteritems():
- if key in result:
- # We do this to not clobber any lists that are
- # *actual* values in this dictionary:
- if key in multi:
- result[key].append(value)
- else:
- result[key] = [result[key], value]
- multi[key] = None
- else:
- result[key] = value
- return result
-
- def dict_of_lists(self):
- """
- Returns a dictionary where each key is associated with a
- list of values.
- """
- result = {}
- for key, value in self.iteritems():
- if key in result:
- result[key].append(value)
- else:
- result[key] = [value]
- return result
-
- def __delitem__(self, key):
- items = self._items
- found = False
- for i in range(len(items)-1, -1, -1):
- if items[i][0] == key:
- del items[i]
- found = True
- if not found:
- raise KeyError(key)
-
- def __contains__(self, key):
- for k, v in self._items:
- if k == key:
- return True
- return False
-
- has_key = __contains__
-
- def clear(self):
- self._items = []
-
- def copy(self):
- return self.__class__(self)
-
- def setdefault(self, key, default=None):
- for k, v in self._items:
- if key == k:
- return v
- self._items.append((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))
- for i in range(len(self._items)):
- if self._items[i][0] == key:
- v = self._items[i][1]
- del self._items[i]
- return v
- if args:
- return args[0]
- else:
- raise KeyError(key)
-
- def popitem(self):
- return self._items.pop()
-
- def update(self, other=None, **kwargs):
- if other is None:
- pass
- elif hasattr(other, 'items'):
- self._items.extend(other.items())
- elif hasattr(other, 'keys'):
- for k in other.keys():
- self._items.append((k, other[k]))
- else:
- for k, v in other:
- self._items.append((k, v))
- if kwargs:
- self.update(kwargs)
-
- def __repr__(self):
- items = ', '.join(['(%r, %r)' % v for v in self.iteritems()])
- return '%s([%s])' % (self.__class__.__name__, items)
-
- def __len__(self):
- return len(self._items)
-
- ##
- ## All the iteration:
- ##
-
- def keys(self):
- return [k for k, v in self._items]
-
- def iterkeys(self):
- for k, v in self._items:
- yield k
-
- __iter__ = iterkeys
-
- def items(self):
- return self._items[:]
-
- def iteritems(self):
- return iter(self._items)
-
- def values(self):
- return [v for k, v in self._items]
-
- def itervalues(self):
- for k, v in self._items:
- yield v
-
-class UnicodeMultiDict(DictMixin):
- """
- A MultiDict wrapper that decodes returned values to unicode on the
- fly. Decoding is not applied to assigned values.
-
- The key/value contents are assumed to be ``str``/``strs`` or
- ``str``/``FieldStorages`` (as is returned by the ``paste.request.parse_``
- functions).
-
- Can optionally also decode keys when the ``decode_keys`` argument is
- True.
-
- ``FieldStorage`` instances are cloned, and the clone's ``filename``
- variable is decoded. Its ``name`` variable is decoded when ``decode_keys``
- is enabled.
-
- """
- def __init__(self, multi=None, encoding=None, errors='strict',
- decode_keys=False):
- self.multi = multi
- if encoding is None:
- encoding = sys.getdefaultencoding()
- self.encoding = encoding
- self.errors = errors
- self.decode_keys = decode_keys
-
- def _decode_key(self, key):
- if self.decode_keys:
- try:
- key = key.decode(self.encoding, self.errors)
- except AttributeError:
- pass
- return key
-
- def _encode_key(self, key):
- if self.decode_keys and isinstance(key, unicode):
- return key.encode(self.encoding, self.errors)
- return key
-
- def _decode_value(self, value):
- """
- Decode the specified value to unicode. Assumes value is a ``str`` or
- `FieldStorage`` object.
-
- ``FieldStorage`` objects are specially handled.
- """
- if isinstance(value, cgi.FieldStorage):
- # decode FieldStorage's field name and filename
- value = copy.copy(value)
- if self.decode_keys:
- value.name = value.name.decode(self.encoding, self.errors)
- if value.filename:
- value.filename = value.filename.decode(self.encoding,
- self.errors)
- elif not isinstance(value, unicode):
- try:
- value = value.decode(self.encoding, self.errors)
- except AttributeError:
- pass
- return value
-
- def _encode_value(self, value):
- # FIXME: should this do the FieldStorage stuff too?
- if isinstance(value, unicode):
- value = value.encode(self.encoding, self.errors)
- return value
-
- def __getitem__(self, key):
- return self._decode_value(self.multi.__getitem__(self._encode_key(key)))
-
- def __setitem__(self, key, value):
- self.multi.__setitem__(self._encode_key(key), self._encode_value(value))
-
- def add(self, key, value):
- """
- Add the key and value, not overwriting any previous value.
- """
- self.multi.add(self._encode_key(key), self._encode_value(value))
-
- def getall(self, key):
- """
- Return a list of all values matching the key (may be an empty list)
- """
- return [self._decode_value(v) for v in self.multi.getall(self._encode_key(key))]
-
- def getone(self, key):
- """
- Get one value matching the key, raising a KeyError if multiple
- values were found.
- """
- return self._decode_value(self.multi.getone(self._encode_key(key)))
-
- def mixed(self):
- """
- Returns a dictionary where the values are either single
- values, or a list of values when a key/value appears more than
- once in this dictionary. This is similar to the kind of
- dictionary often used to represent the variables in a web
- request.
- """
- unicode_mixed = {}
- for key, value in self.multi.mixed().iteritems():
- if isinstance(value, list):
- value = [self._decode_value(value) for value in value]
- else:
- value = self._decode_value(value)
- unicode_mixed[self._decode_key(key)] = value
- return unicode_mixed
-
- def dict_of_lists(self):
- """
- Returns a dictionary where each key is associated with a
- list of values.
- """
- unicode_dict = {}
- for key, value in self.multi.dict_of_lists().iteritems():
- value = [self._decode_value(value) for value in value]
- unicode_dict[self._decode_key(key)] = value
- return unicode_dict
-
- def __delitem__(self, key):
- self.multi.__delitem__(self._encode_key(key))
-
- def __contains__(self, key):
- return self.multi.__contains__(self._encode_key(key))
-
- has_key = __contains__
-
- def clear(self):
- self.multi.clear()
-
- def copy(self):
- return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors)
-
- def setdefault(self, key, default=None):
- return self._decode_value(self.multi.setdefault(self._encode_key(key), self._encode_value(default)))
-
- def pop(self, key, *args):
- return self._decode_value(self.multi.pop(self._encode_key(key), *args))
-
- def popitem(self):
- k, v = self.multi.popitem()
- return (self._decode_key(k), self._decode_value(v))
-
- def __repr__(self):
- items = ', '.join(['(%r, %r)' % v for v in self.items()])
- return '%s([%s])' % (self.__class__.__name__, items)
-
- def __len__(self):
- return self.multi.__len__()
-
- ##
- ## All the iteration:
- ##
-
- def keys(self):
- return [self._decode_key(k) for k in self.multi.iterkeys()]
-
- def iterkeys(self):
- for k in self.multi.iterkeys():
- yield self._decode_key(k)
-
- __iter__ = iterkeys
-
- def items(self):
- return [(self._decode_key(k), self._decode_value(v))
- for k, v in self.multi.iteritems()]
-
- def iteritems(self):
- for k, v in self.multi.iteritems():
- yield (self._decode_key(k), self._decode_value(v))
-
- def values(self):
- return [self._decode_value(v) for v in self.multi.itervalues()]
-
- def itervalues(self):
- for v in self.multi.itervalues():
- yield self._decode_value(v)
-
-_dummy = object()
-
-class NestedMultiDict(MultiDict):
- """
- Wraps several MultiDict objects, treating it as one large MultiDict
- """
-
- def __init__(self, *dicts):
- self.dicts = dicts
-
- def __getitem__(self, key):
- for d in self.dicts:
- value = d.get(key, _dummy)
- if value is not _dummy:
- return value
- raise KeyError(key)
-
- def _readonly(self, *args, **kw):
- raise KeyError("NestedMultiDict objects are read-only")
- __setitem__ = _readonly
- add = _readonly
- __delitem__ = _readonly
- clear = _readonly
- setdefault = _readonly
- pop = _readonly
- popitem = _readonly
- update = _readonly
-
- def getall(self, key):
- result = []
- for d in self.dicts:
- result.extend(d.getall(key))
- return result
-
- # Inherited:
- # getone
- # mixed
- # dict_of_lists
-
- def copy(self):
- return MultiDict(self)
-
- def __contains__(self, key):
- for d in self.dicts:
- if key in d:
- return True
- return False
-
- has_key = __contains__
-
- def __len__(self):
- v = 0
- for d in self.dicts:
- v += len(d)
- return v
-
- def __nonzero__(self):
- for d in self.dicts:
- if d:
- return True
- return False
-
- def items(self):
- return list(self.iteritems())
-
- def iteritems(self):
- for d in self.dicts:
- for item in d.iteritems():
- yield item
-
- def values(self):
- return list(self.itervalues())
-
- def itervalues(self):
- for d in self.dicts:
- for value in d.itervalues():
- yield value
-
- def keys(self):
- return list(self.iterkeys())
-
- def __iter__(self):
- for d in self.dicts:
- for key in d:
- yield key
-
- iterkeys = __iter__
-
-class NoVars(object):
- """
- Represents no variables; used when no variables
- are applicable.
-
- This is read-only
- """
-
- def __init__(self, reason=None):
- self.reason = reason or 'N/A'
-
- def __getitem__(self, key):
- raise KeyError("No key %r: %s" % (key, self.reason))
-
- def __setitem__(self, *args, **kw):
- raise KeyError("Cannot add variables: %s" % self.reason)
-
- add = __setitem__
- setdefault = __setitem__
- update = __setitem__
-
- def __delitem__(self, *args, **kw):
- raise KeyError("No keys to delete: %s" % self.reason)
- clear = __delitem__
- pop = __delitem__
- popitem = __delitem__
-
- def get(self, key, default=None):
- return default
-
- def getall(self, key):
- return []
-
- def getone(self, key):
- return self[key]
-
- def mixed(self):
- return {}
- dict_of_lists = mixed
-
- def __contains__(self, key):
- return False
- has_key = __contains__
-
- def copy(self):
- return self
-
- def __repr__(self):
- return '<%s: %s>' % (self.__class__.__name__,
- self.reason)
-
- def __len__(self):
- return 0
-
- def __cmp__(self, other):
- return cmp({}, other)
-
- def keys(self):
- return []
- def iterkeys(self):
- return iter([])
- __iter__ = iterkeys
- items = keys
- iteritems = iterkeys
- values = keys
- itervalues = iterkeys
-
-__test__ = {
- 'general': """
- >>> d = MultiDict(a=1, b=2)
- >>> d['a']
- 1
- >>> d.getall('c')
- []
- >>> d.add('a', 2)
- >>> d['a']
- 2
- >>> d.getall('a')
- [1, 2]
- >>> d['b'] = 4
- >>> d.getall('b')
- [4]
- >>> d.keys()
- ['a', 'a', 'b']
- >>> d.items()
- [('a', 1), ('a', 2), ('b', 4)]
- >>> d.mixed() == {'a': [1, 2], 'b': 4}
- True
- >>> MultiDict([('a', 'b')], c=2)
- MultiDict([('a', 'b'), ('c', 2)])
- """}
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/lib/webob/statusreasons.py b/lib/webob/statusreasons.py
@@ -1,67 +0,0 @@
-"""
-Gives ``status_reasons``, a dictionary of HTTP reasons for integer status codes
-"""
-
-__all__ = ['status_reasons']
-
-status_reasons = {
- # Status Codes
- # Informational
- 100: 'Continue',
- 101: 'Switching Protocols',
- 102: 'Processing',
-
- # Successful
- 200: 'OK',
- 201: 'Created',
- 202: 'Accepted',
- 203: 'Non Authoritative Information',
- 204: 'No Content',
- 205: 'Reset Content',
- 206: 'Partial Content',
- 207: 'Multi Status',
- 226: 'IM Used',
-
- # Redirection
- 300: 'Multiple Choices',
- 301: 'Moved Permanently',
- 302: 'Found',
- 303: 'See Other',
- 304: 'Not Modified',
- 305: 'Use Proxy',
- 307: 'Temporary Redirect',
-
- # Client Error
- 400: 'Bad Request',
- 401: 'Unauthorized',
- 402: 'Payment Required',
- 403: 'Forbidden',
- 404: 'Not Found',
- 405: 'Method Not Allowed',
- 406: 'Not Acceptable',
- 407: 'Proxy Authentication Required',
- 408: 'Request Timeout',
- 409: 'Conflict',
- 410: 'Gone',
- 411: 'Length Required',
- 412: 'Precondition Failed',
- 413: 'Request Entity Too Large',
- 414: 'Request URI Too Long',
- 415: 'Unsupported Media Type',
- 416: 'Requested Range Not Satisfiable',
- 417: 'Expectation Failed',
- 422: 'Unprocessable Entity',
- 423: 'Locked',
- 424: 'Failed Dependency',
- 426: 'Upgrade Required',
-
- # Server Error
- 500: 'Internal Server Error',
- 501: 'Not Implemented',
- 502: 'Bad Gateway',
- 503: 'Service Unavailable',
- 504: 'Gateway Timeout',
- 505: 'HTTP Version Not Supported',
- 507: 'Insufficient Storage',
- 510: 'Not Extended',
- }
diff --git a/lib/webob/updatedict.py b/lib/webob/updatedict.py
@@ -1,41 +0,0 @@
-"""
-Dict that has a callback on all updates
-"""
-
-class UpdateDict(dict):
- updated = None
- updated_args = None
- def _updated(self):
- """
- Assign to new_dict.updated to track updates
- """
- updated = self.updated
- if updated is not None:
- args = self.updated_args
- if args is None:
- args = (self,)
- updated(*args)
- def __setitem__(self, key, item):
- dict.__setitem__(self, key, item)
- self._updated()
- def __delitem__(self, key):
- dict.__delitem__(self, key)
- self._updated()
- def clear(self):
- dict.clear(self)
- self._updated()
- def update(self, *args, **kw):
- dict.update(self, *args, **kw)
- self._updated()
- def setdefault(self, key, failobj=None):
- dict.setdefault(self, key, failobj)
- self._updated()
- def pop(self):
- v = dict.pop(self)
- self._updated()
- return v
- def popitem(self):
- v = dict.popitem(self)
- self._updated()
- return v
-
diff --git a/lib/webob/util/__init__.py b/lib/webob/util/__init__.py
@@ -1 +0,0 @@
-#
diff --git a/lib/webob/util/dictmixin.py b/lib/webob/util/dictmixin.py
@@ -1,102 +0,0 @@
-"""
-A backport of UserDict.DictMixin for pre-python-2.4
-"""
-__all__ = ['DictMixin']
-
-try:
- from UserDict import DictMixin
-except ImportError:
- 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=None):
- 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=None, **kwargs):
- # Make progressively weaker assumptions about "other"
- if other is None:
- pass
- elif hasattr(other, 'iteritems'): # iteritems saves memory and lookups
- for k, v in other.iteritems():
- self[k] = v
- elif hasattr(other, 'keys'):
- for k in other.keys():
- self[k] = other[k]
- else:
- for k, v in other:
- self[k] = v
- if kwargs:
- self.update(kwargs)
- 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())
diff --git a/lib/webob/util/reversed.py b/lib/webob/util/reversed.py
@@ -1,4 +0,0 @@
-## Backport of reversed
-
-def reversed(seq):
- return iter(list(seq)[::-1])
diff --git a/lib/webob/util/safegzip.py b/lib/webob/util/safegzip.py
@@ -1,21 +0,0 @@
-"""
-GZip that doesn't include the timestamp
-"""
-import gzip
-
-class GzipFile(gzip.GzipFile):
-
- def _write_gzip_header(self):
- self.fileobj.write('\037\213') # magic header
- self.fileobj.write('\010') # compression method
- fname = self.filename[:-3]
- flags = 0
- if fname:
- flags = gzip.FNAME
- self.fileobj.write(chr(flags))
- ## This is what WebOb patches:
- gzip.write32u(self.fileobj, long(0))
- self.fileobj.write('\002')
- self.fileobj.write('\377')
- if fname:
- self.fileobj.write(fname + '\000')
diff --git a/lib/webob/util/stringtemplate.py b/lib/webob/util/stringtemplate.py
@@ -1,128 +0,0 @@
-"""
-Just string.Template, backported for use with Python 2.3
-"""
-####################################################################
-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)
-