constance

Scripts for generating (an earlier obsolete version of) my personal web site
git clone https://code.djc.id.au/git/constance/
commit 5fbfd1786405e2b81bedc879a852cd6d57449ec8
parent 44bf4a60c35f425f40429bded509c9aef9509b57
Author: Dan Callaghan <djc@djc.id.au>
Date:   Sat, 22 Nov 2008 13:55:14 +1000

Paste-1.7.2 from http://pypi.python.org/packages/source/P/Paste/Paste-1.7.2.tar.gz

Diffstat:
Alib/Paste.egg-info/PKG-INFO | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/Paste.egg-info/SOURCES.txt | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/Paste.egg-info/dependency_links.txt | 1+
Alib/Paste.egg-info/entry_points.txt | 47+++++++++++++++++++++++++++++++++++++++++++++++
Alib/Paste.egg-info/namespace_packages.txt | 1+
Alib/Paste.egg-info/not-zip-safe | 1+
Alib/Paste.egg-info/requires.txt | 15+++++++++++++++
Alib/Paste.egg-info/top_level.txt | 1+
Alib/paste/__init__.py | 17+++++++++++++++++
Alib/paste/auth/__init__.py | 9+++++++++
Alib/paste/auth/auth_tkt.py | 352+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/auth/basic.py | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/auth/cas.py | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/auth/cookie.py | 395+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/auth/digest.py | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/auth/form.py | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/auth/grantip.py | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/auth/multi.py | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/auth/open_id.py | 412+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/cascade.py | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/cgiapp.py | 271+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/cgitb_catcher.py | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/config.py | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/debug/__init__.py | 5+++++
Alib/paste/debug/debugapp.py | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/debug/doctest_webapp.py | 435+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/debug/fsdiff.py | 409+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/debug/prints.py | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/debug/profile.py | 227+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/debug/testserver.py | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/debug/watchthreads.py | 347+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/debug/wdg_validate.py | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/errordocument.py | 372+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/evalexception/__init__.py | 7+++++++
Alib/paste/evalexception/evalcontext.py | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/evalexception/media/debug.js | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/evalexception/media/minus.jpg | 0
Alib/paste/evalexception/media/plus.jpg | 0
Alib/paste/evalexception/middleware.py | 611+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/evalexception/mochikit/MochiKit.js | 4802+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/evalexception/mochikit/__package__.js | 2++
Alib/paste/exceptions/__init__.py | 6++++++
Alib/paste/exceptions/collector.py | 526+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/exceptions/errormiddleware.py | 460+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/exceptions/formatter.py | 564+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/exceptions/reporter.py | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/exceptions/serial_number_generator.py | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/fileapp.py | 349+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/fixture.py | 1723+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/flup_session.py | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/gzipper.py | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/httpexceptions.py | 664+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/httpheaders.py | 1097+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/httpserver.py | 1406+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/lint.py | 436+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/modpython.py | 252+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/pony.py | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/progress.py | 222+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/proxy.py | 276+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/recursive.py | 401+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/registry.py | 552+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/reloader.py | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/request.py | 405+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/response.py | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/session.py | 337+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/transaction.py | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/translogger.py | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/url.py | 475+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/urlmap.py | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/urlparser.py | 652+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/PySourceColor.py | 2103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/UserDict24.py | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/__init__.py | 4++++
Alib/paste/util/classinit.py | 42++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/classinstance.py | 38++++++++++++++++++++++++++++++++++++++
Alib/paste/util/converters.py | 26++++++++++++++++++++++++++
Alib/paste/util/dateinterval.py | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/datetimeutil.py | 361+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/doctest24.py | 2665+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/filemixin.py | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/finddata.py | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/findpackage.py | 26++++++++++++++++++++++++++
Alib/paste/util/import_string.py | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/intset.py | 511+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/ip4.py | 273+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/killthread.py | 30++++++++++++++++++++++++++++++
Alib/paste/util/looper.py | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/mimeparse.py | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/multidict.py | 397+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/quoting.py | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/scgiserver.py | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/string24.py | 531+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/subprocess24.py | 1152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/template.py | 758+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/threadedprint.py | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/util/threadinglocal.py | 43+++++++++++++++++++++++++++++++++++++++++++
Alib/paste/wsgilib.py | 597+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/paste/wsgiwrappers.py | 581+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
98 files changed, 34637 insertions(+), 0 deletions(-)
diff --git a/lib/Paste.egg-info/PKG-INFO b/lib/Paste.egg-info/PKG-INFO
@@ -0,0 +1,119 @@
+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
@@ -0,0 +1,232 @@
+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
@@ -0,0 +1 @@
+
diff --git a/lib/Paste.egg-info/entry_points.txt b/lib/Paste.egg-info/entry_points.txt
@@ -0,0 +1,46 @@
+
+      [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
@@ -0,0 +1 @@
+paste
diff --git a/lib/Paste.egg-info/not-zip-safe b/lib/Paste.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/lib/Paste.egg-info/requires.txt b/lib/Paste.egg-info/requires.txt
@@ -0,0 +1,15 @@
+
+
+[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
@@ -0,0 +1 @@
+paste
diff --git a/lib/paste/__init__.py b/lib/paste/__init__.py
@@ -0,0 +1,17 @@
+# (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
@@ -0,0 +1,9 @@
+# (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
@@ -0,0 +1,352 @@
+# (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
@@ -0,0 +1,122 @@
+# (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
@@ -0,0 +1,99 @@
+# (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
@@ -0,0 +1,395 @@
+# (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
@@ -0,0 +1,213 @@
+# (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
@@ -0,0 +1,149 @@
+# (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
@@ -0,0 +1,113 @@
+# (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
@@ -0,0 +1,79 @@
+# (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
@@ -0,0 +1,412 @@
+# (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&nbsp;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
@@ -0,0 +1,133 @@
+# (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
@@ -0,0 +1,271 @@
+# (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
@@ -0,0 +1,116 @@
+# (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
@@ -0,0 +1,120 @@
+# (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
@@ -0,0 +1,5 @@
+# (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
@@ -0,0 +1,79 @@
+# (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
@@ -0,0 +1,435 @@
+# (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
@@ -0,0 +1,409 @@
+# (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
@@ -0,0 +1,148 @@
+# (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('  ', '&nbsp; ')
+        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
@@ -0,0 +1,227 @@
+# (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
@@ -0,0 +1,93 @@
+# (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
@@ -0,0 +1,347 @@
+"""
+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 = \'&#9662; Hide environ\';
+        } else {
+            el.style.display = 'none';
+            this.innerHTML = \'&#9656; Show environ\';
+        }
+        return false
+      ">&#9656; 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 = \'&#9662; Hide traceback\';
+        } else {
+            el.style.display = 'none';
+            this.innerHTML = \'&#9656; Show traceback\';
+        }
+        return false
+      ">&#9656; 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
@@ -0,0 +1,117 @@
+# (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
@@ -0,0 +1,372 @@
+# (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
@@ -0,0 +1,7 @@
+# (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
@@ -0,0 +1,68 @@
+# (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
@@ -0,0 +1,161 @@
+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
@@ -0,0 +1,611 @@
+# (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 ``&nbsp;``).
+
+    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 '&nbsp;'
+    return match.group(1) + '&nbsp;' * (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">&gt;&gt;&gt;</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)">&nbsp; &nbsp; '
+                '<img src="%s/_debug/media/plus.jpg" border=0 width=9 '
+                'height=9> &nbsp; &nbsp;</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
@@ -0,0 +1,4802 @@
+/***
+
+    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,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
+},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
@@ -0,0 +1,2 @@
+dojo.hostenv.conditionalLoadModule({"common": ["MochiKit.MochiKit"]});
+dojo.hostenv.moduleLoaded("MochiKit.*");
diff --git a/lib/paste/exceptions/__init__.py b/lib/paste/exceptions/__init__.py
@@ -0,0 +1,6 @@
+# (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
@@ -0,0 +1,526 @@
+# (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
@@ -0,0 +1,460 @@
+# (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
@@ -0,0 +1,564 @@
+# (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="#">&lt;&lt;&nbsp; </a>%s</code>'
+                '<code class="source" source-type="short"><a onclick="return switch_source(this, \'short\')" class="switch_source" href="#">&gt;&gt;&nbsp; </a>%s</code>'
+                % (q_long_source,
+                   q_source))
+    def format_source(self, source_line):
+        return '&nbsp;&nbsp;<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&nbsp; &nbsp; %s]</tt>' % (
+                title, ',<br>&nbsp; &nbsp; '.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: '&nbsp;'*(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 &#8203;
+    #   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
@@ -0,0 +1,142 @@
+# (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
@@ -0,0 +1,123 @@
+# (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
@@ -0,0 +1,349 @@
+# (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
@@ -0,0 +1,1723 @@
+# (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 [('&nbsp;', ' '), ('&gt;', '>'),
+                      ('&lt;', '<'), ('&quot;', '"'),
+                      ('&amp;', '&')]:
+        v = v.replace(ent, repl)
+    return v
diff --git a/lib/paste/flup_session.py b/lib/paste/flup_session.py
@@ -0,0 +1,108 @@
+# (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
@@ -0,0 +1,111 @@
+# (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
@@ -0,0 +1,664 @@
+# (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
@@ -0,0 +1,1097 @@
+# (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
@@ -0,0 +1,1406 @@
+# (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
@@ -0,0 +1,436 @@
+# (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
@@ -0,0 +1,252 @@
+"""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
@@ -0,0 +1,57 @@
+# (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
@@ -0,0 +1,222 @@
+# (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
@@ -0,0 +1,276 @@
+# (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
@@ -0,0 +1,401 @@
+# (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
@@ -0,0 +1,552 @@
+# (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
@@ -0,0 +1,178 @@
+# (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
@@ -0,0 +1,405 @@
+# (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
@@ -0,0 +1,240 @@
+# (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
@@ -0,0 +1,337 @@
+# (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
@@ -0,0 +1,120 @@
+# (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
@@ -0,0 +1,116 @@
+# (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
@@ -0,0 +1,475 @@
+# (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
@@ -0,0 +1,251 @@
+# (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
@@ -0,0 +1,652 @@
+# (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
@@ -0,0 +1,2103 @@
+# -*- 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("&", "&amp;")
+     text = text.replace("<", "&lt;")
+     text = text.replace(">", "&gt;")
+     return text
+
+def unescape(text):
+     """unsecape escaped text"""
+     text = text.replace("&quot;", '"')
+     text = text.replace("&gt;", ">")
+     text = text.replace("&lt;", "<")
+     text = text.replace("&amp;", "&")
+     return text
+
+########################################################### Custom Exceptions
+
+class PySourceColorError(Exception):
+    # Base for custom errors
+    def __init__(self, msg=''):
+        self._msg = msg
+        Exception.__init__(self, msg)
+    def __repr__(self):
+        return self._msg
+    __str__ = __repr__
+
+class PathError(PySourceColorError):
+    def __init__(self, msg):
+       PySourceColorError.__init__(self,
+         'Path error! : %s'% msg)
+
+class InputError(PySourceColorError):
+   def __init__(self, msg):
+       PySourceColorError.__init__(self,
+         'Input error! : %s'% msg)
+
+########################################################## Python code parser
+
+class Parser(object):
+
+    """MoinMoin python parser heavily chopped :)"""
+
+    def __init__(self, raw, colors=None, title='', out=sys.stdout,
+                   markup='html', header=None, footer=None, linenumbers=0):
+        """Store the source text & set some flags"""
+        if colors == None:
+            colors = defaultColors
+        self.raw = raw.expandtabs().rstrip()
+        self.title = os.path.basename(title)
+        self.out = out
+        self.line = ''
+        self.lasttext = ''
+        self.argFlag = 0
+        self.classFlag = 0
+        self.defFlag = 0
+        self.decoratorFlag = 0
+        self.external = 0
+        self.markup = markup.upper()
+        self.colors = colors
+        self.header = header
+        self.footer = footer
+        self.doArgs = 1 #  overrides the new tokens
+        self.doNames = 1 #  overrides the new tokens
+        self.doMathOps = 1 #  overrides the new tokens
+        self.doBrackets = 1 #  overrides the new tokens
+        self.doURL = 1 # override url conversion
+        self.LINENUMHOLDER = "___line___".upper()
+        self.LINESTART = "___start___".upper()
+        self.skip = 0
+        # add space left side of code for padding.Override in color dict.
+        self.extraspace = self.colors.get(EXTRASPACE, '')
+        # Linenumbers less then zero also have numberlinks
+        self.dolinenums = self.linenum = abs(linenumbers)
+        if linenumbers < 0:
+            self.numberlinks = 1
+        else:
+            self.numberlinks = 0
+
+    def format(self, form=None):
+        """Parse and send the colorized source"""
+        if form in ('snip','code'):
+            self.addEnds = 0
+        elif form == 'embed':
+            self.addEnds = 0
+            self.external = 1
+        else:
+            if form == 'external':
+                self.external = 1
+            self.addEnds = 1
+
+        # Store line offsets in self.lines
+        self.lines = [0, 0]
+        pos = 0
+
+        # Add linenumbers
+        if self.dolinenums:
+            start=self.LINENUMHOLDER+' '+self.extraspace
+        else:
+            start=''+self.extraspace
+        newlines = []
+        lines = self.raw.splitlines(0)
+        for l in lines:
+             # span and div escape for customizing and embedding raw text 
+             if (l.startswith('#$#')
+                  or l.startswith('#%#')
+                  or l.startswith('#@#')):   
+                newlines.append(l)
+             else:
+                # kludge for line spans in css,xhtml
+                if self.markup in ['XHTML','CSS']:
+                    newlines.append(self.LINESTART+' '+start+l)
+                else:
+                    newlines.append(start+l)
+        self.raw = "\n".join(newlines)+'\n'# plus an extra newline at the end
+
+        # Gather lines
+        while 1:
+            pos = self.raw.find('\n', pos) + 1
+            if not pos: break
+            self.lines.append(pos)
+        self.lines.append(len(self.raw))
+
+        # Wrap text in a filelike object
+        self.pos = 0
+        text = StringIO.StringIO(self.raw)
+        
+        # Markup start
+        if self.addEnds:
+            self._doPageStart()
+        else:
+            self._doSnippetStart()
+
+        ## Tokenize calls the __call__
+        ## function for each token till done.
+        # Parse the source and write out the results.
+        try:
+            tokenize.tokenize(text.readline, self)
+        except tokenize.TokenError, 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
@@ -0,0 +1,167 @@
+"""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
@@ -0,0 +1,4 @@
+"""
+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
@@ -0,0 +1,42 @@
+# (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
@@ -0,0 +1,38 @@
+# (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
@@ -0,0 +1,26 @@
+# (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
@@ -0,0 +1,103 @@
+"""
+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
@@ -0,0 +1,361 @@
+# (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
@@ -0,0 +1,2665 @@
+# 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
@@ -0,0 +1,53 @@
+# (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
@@ -0,0 +1,99 @@
+# (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
@@ -0,0 +1,26 @@
+# (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
@@ -0,0 +1,95 @@
+# (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
@@ -0,0 +1,511 @@
+# -*- 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
@@ -0,0 +1,273 @@
+# -*- 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
@@ -0,0 +1,30 @@
+"""
+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
@@ -0,0 +1,152 @@
+"""
+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
@@ -0,0 +1,178 @@
+"""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
@@ -0,0 +1,397 @@
+# (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
@@ -0,0 +1,80 @@
+# (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!>')
+    '&lt;hey!&gt;'
+    >>> 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('&lt;hey&nbsp;you&gt;')
+    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
@@ -0,0 +1,172 @@
+#! /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
@@ -0,0 +1,531 @@
+"""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
@@ -0,0 +1,1152 @@
+# 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
@@ -0,0 +1,758 @@
+"""
+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
@@ -0,0 +1,250 @@
+# (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
@@ -0,0 +1,43 @@
+# (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
@@ -0,0 +1,597 @@
+# (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
@@ -0,0 +1,581 @@
+# (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',
+}