From 42b22881290e00e06b840dee1e42f0f5ef044d47 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Mon, 7 Mar 2016 14:05:52 -0800 Subject: tox.ini: Add py35 to envlist --- .hgignore | 14 + MANIFEST.in | 10 + README.rst | 109 + docs/DeveloperGuidelines.txt | 110 + docs/StyleGuide.txt | 102 + docs/_static/paste.css | 15 + docs/_templates/layout.html | 29 + docs/community/index.txt | 15 + docs/community/mailing-list.txt | 14 + docs/community/repository.txt | 10 + docs/conf.py | 132 + docs/default.css | 394 + docs/developer-features.txt | 148 + docs/do-it-yourself-framework.txt | 538 ++ docs/download/index.txt | 32 + docs/future.txt | 108 + docs/include/contact.txt | 5 + docs/include/reference_header.txt | 5 + docs/index.txt | 69 + docs/license.txt | 20 + docs/modules/auth.auth_tkt.txt | 14 + docs/modules/auth.basic.txt | 11 + docs/modules/auth.cas.txt | 11 + docs/modules/auth.cookie.txt | 12 + docs/modules/auth.digest.txt | 12 + docs/modules/auth.form.txt | 10 + docs/modules/auth.grantip.txt | 10 + docs/modules/auth.multi.txt | 11 + docs/modules/cascade.txt | 10 + docs/modules/cgiapp.txt | 11 + docs/modules/cgitb_catcher.txt | 10 + docs/modules/debug.debugapp.txt | 13 + docs/modules/debug.fsdiff.txt | 15 + docs/modules/debug.prints.txt | 10 + docs/modules/debug.profile.txt | 13 + docs/modules/debug.watchthreads.txt | 12 + docs/modules/debug.wdg_validate.txt | 11 + docs/modules/errordocument.txt | 12 + docs/modules/evalexception.txt | 9 + docs/modules/exceptions.txt | 48 + docs/modules/fileapp.txt | 15 + docs/modules/fixture.txt | 40 + docs/modules/gzipper.txt | 10 + docs/modules/httpexceptions.txt | 49 + docs/modules/httpheaders.txt | 8 + docs/modules/httpserver.txt | 10 + docs/modules/lint.txt | 10 + docs/modules/pony.txt | 10 + docs/modules/progress.txt | 13 + docs/modules/proxy.txt | 14 + docs/modules/recursive.txt | 10 + docs/modules/registry.txt | 13 + docs/modules/reloader.txt | 14 + docs/modules/request.txt | 19 + docs/modules/response.txt | 15 + docs/modules/session.txt | 11 + docs/modules/transaction.txt | 11 + docs/modules/translogger.txt | 10 + docs/modules/url.txt | 10 + docs/modules/urlmap.txt | 11 + docs/modules/urlparser.txt | 14 + docs/modules/util.import_string.txt | 12 + docs/modules/util.multidict.txt | 11 + docs/modules/wsgilib.txt | 18 + docs/modules/wsgiwrappers.txt | 10 + docs/news.txt | 1047 +++ docs/paste-httpserver-threadpool.txt | 150 + docs/test_server.ini | 42 + docs/testing-applications.txt | 156 + docs/url-parsing-with-wsgi.txt | 304 + docs/web/default-site.css | 382 + docs/web/site.js | 69 + docs/web/style.css | 90 + paste/__init__.py | 17 + paste/auth/__init__.py | 9 + paste/auth/auth_tkt.py | 429 ++ paste/auth/basic.py | 122 + paste/auth/cas.py | 99 + paste/auth/cookie.py | 405 + paste/auth/digest.py | 254 + paste/auth/form.py | 149 + paste/auth/grantip.py | 114 + paste/auth/multi.py | 79 + paste/auth/open_id.py | 413 ++ paste/cascade.py | 133 + paste/cgiapp.py | 280 + paste/cgitb_catcher.py | 121 + paste/config.py | 120 + paste/cowbell/__init__.py | 104 + paste/cowbell/bell-ascending.png | Bin 0 -> 132993 bytes paste/cowbell/bell-descending.png | Bin 0 -> 124917 bytes paste/debug/__init__.py | 5 + paste/debug/debugapp.py | 79 + paste/debug/doctest_webapp.py | 432 ++ paste/debug/fsdiff.py | 408 + paste/debug/prints.py | 149 + paste/debug/profile.py | 228 + paste/debug/testserver.py | 93 + paste/debug/watchthreads.py | 347 + paste/debug/wdg_validate.py | 118 + paste/errordocument.py | 389 + paste/evalexception/__init__.py | 7 + paste/evalexception/evalcontext.py | 69 + paste/evalexception/media/MochiKit.packed.js | 7829 ++++++++++++++++++++ paste/evalexception/media/debug.js | 161 + paste/evalexception/media/minus.jpg | Bin 0 -> 359 bytes paste/evalexception/media/plus.jpg | Bin 0 -> 361 bytes paste/evalexception/middleware.py | 618 ++ paste/exceptions/__init__.py | 6 + paste/exceptions/collector.py | 523 ++ paste/exceptions/errormiddleware.py | 466 ++ paste/exceptions/formatter.py | 565 ++ paste/exceptions/reporter.py | 141 + paste/exceptions/serial_number_generator.py | 129 + paste/fileapp.py | 356 + paste/fixture.py | 1755 +++++ paste/flup_session.py | 108 + paste/gzipper.py | 107 + paste/httpexceptions.py | 667 ++ paste/httpheaders.py | 1116 +++ paste/httpserver.py | 1430 ++++ paste/lint.py | 438 ++ paste/modpython.py | 253 + paste/pony.py | 57 + paste/progress.py | 222 + paste/proxy.py | 289 + paste/recursive.py | 406 + paste/registry.py | 581 ++ paste/reloader.py | 179 + paste/request.py | 428 ++ paste/response.py | 240 + paste/session.py | 346 + paste/transaction.py | 120 + paste/translogger.py | 122 + paste/url.py | 478 ++ paste/urlmap.py | 263 + paste/urlparser.py | 639 ++ paste/util/PySourceColor.py | 2102 ++++++ paste/util/__init__.py | 4 + paste/util/classinit.py | 42 + paste/util/classinstance.py | 38 + paste/util/converters.py | 30 + paste/util/dateinterval.py | 104 + paste/util/datetimeutil.py | 359 + paste/util/filemixin.py | 53 + paste/util/finddata.py | 98 + paste/util/findpackage.py | 26 + paste/util/import_string.py | 95 + paste/util/intset.py | 515 ++ paste/util/ip4.py | 274 + paste/util/killthread.py | 30 + paste/util/looper.py | 156 + paste/util/mimeparse.py | 160 + paste/util/multidict.py | 429 ++ paste/util/quoting.py | 85 + paste/util/scgiserver.py | 172 + paste/util/template.py | 756 ++ paste/util/threadedprint.py | 250 + paste/util/threadinglocal.py | 43 + paste/wsgilib.py | 600 ++ paste/wsgiwrappers.py | 590 ++ regen-docs | 9 + setup.cfg | 8 + setup.py | 116 + tests/__init__.py | 7 + tests/cgiapp_data/error.cgi | 3 + tests/cgiapp_data/form.cgi | 69 + tests/cgiapp_data/ok.cgi | 5 + tests/cgiapp_data/stderr.cgi | 8 + tests/test_auth/__init__.py | 0 tests/test_auth/test_auth_cookie.py | 46 + tests/test_auth/test_auth_digest.py | 93 + tests/test_cgiapp.py | 59 + tests/test_cgitb_catcher.py | 78 + tests/test_config.py | 85 + tests/test_doctests.py | 63 + tests/test_errordocument.py | 92 + tests/test_exceptions/__init__.py | 1 + tests/test_exceptions/test_error_middleware.py | 109 + tests/test_exceptions/test_formatter.py | 183 + tests/test_exceptions/test_httpexceptions.py | 97 + tests/test_exceptions/test_reporter.py | 50 + tests/test_fileapp.py | 242 + tests/test_fixture.py | 28 + tests/test_grantip.py | 37 + tests/test_gzipper.py | 19 + tests/test_httpheaders.py | 159 + tests/test_httpserver.py | 45 + tests/test_import_string.py | 16 + tests/test_multidict.py | 162 + tests/test_profilemiddleware.py | 29 + tests/test_proxy.py | 12 + tests/test_recursive.py | 105 + tests/test_registry.py | 314 + tests/test_request.py | 66 + tests/test_request_form.py | 36 + tests/test_response.py | 11 + tests/test_session.py | 56 + tests/test_template.txt | 136 + tests/test_urlmap.py | 53 + tests/test_urlparser.py | 178 + tests/test_util/__init__.py | 0 tests/test_util/test_datetimeutil.py | 135 + tests/test_util/test_mimeparse.py | 235 + tests/test_util/test_quoting.py | 28 + tests/test_wsgiwrappers.py | 146 + tests/urlparser_data/__init__.py | 1 + tests/urlparser_data/deep/index.html | 1 + tests/urlparser_data/deep/sub/Main.txt | 1 + .../find_file/dir with spaces/test 4.html | 1 + tests/urlparser_data/find_file/index.txt | 1 + tests/urlparser_data/find_file/test 3.html | 1 + tests/urlparser_data/find_file/test2.html | 1 + tests/urlparser_data/hook/__init__.py | 10 + tests/urlparser_data/hook/app.py | 9 + tests/urlparser_data/hook/index.py | 9 + tests/urlparser_data/not_found/__init__.py | 1 + tests/urlparser_data/not_found/recur/__init__.py | 9 + tests/urlparser_data/not_found/recur/isfound.txt | 1 + tests/urlparser_data/not_found/simple/__init__.py | 3 + tests/urlparser_data/not_found/simple/found.txt | 1 + tests/urlparser_data/not_found/user/__init__.py | 12 + tests/urlparser_data/not_found/user/list.py | 8 + tests/urlparser_data/python/__init__.py | 1 + tests/urlparser_data/python/simpleapp.py | 5 + tests/urlparser_data/python/stream.py | 7 + tests/urlparser_data/python/sub/__init__.py | 1 + tests/urlparser_data/python/sub/simpleapp.py | 4 + tests/urlparser_data/secured.txt | 1 + tox.ini | 9 + 230 files changed, 41500 insertions(+) create mode 100644 .hgignore create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 docs/DeveloperGuidelines.txt create mode 100644 docs/StyleGuide.txt create mode 100644 docs/_static/paste.css create mode 100644 docs/_templates/layout.html create mode 100644 docs/community/index.txt create mode 100644 docs/community/mailing-list.txt create mode 100644 docs/community/repository.txt create mode 100644 docs/conf.py create mode 100644 docs/default.css create mode 100644 docs/developer-features.txt create mode 100644 docs/do-it-yourself-framework.txt create mode 100644 docs/download/index.txt create mode 100644 docs/future.txt create mode 100644 docs/include/contact.txt create mode 100644 docs/include/reference_header.txt create mode 100644 docs/index.txt create mode 100644 docs/license.txt create mode 100644 docs/modules/auth.auth_tkt.txt create mode 100644 docs/modules/auth.basic.txt create mode 100644 docs/modules/auth.cas.txt create mode 100644 docs/modules/auth.cookie.txt create mode 100644 docs/modules/auth.digest.txt create mode 100644 docs/modules/auth.form.txt create mode 100644 docs/modules/auth.grantip.txt create mode 100644 docs/modules/auth.multi.txt create mode 100644 docs/modules/cascade.txt create mode 100644 docs/modules/cgiapp.txt create mode 100644 docs/modules/cgitb_catcher.txt create mode 100644 docs/modules/debug.debugapp.txt create mode 100644 docs/modules/debug.fsdiff.txt create mode 100644 docs/modules/debug.prints.txt create mode 100644 docs/modules/debug.profile.txt create mode 100644 docs/modules/debug.watchthreads.txt create mode 100644 docs/modules/debug.wdg_validate.txt create mode 100644 docs/modules/errordocument.txt create mode 100644 docs/modules/evalexception.txt create mode 100644 docs/modules/exceptions.txt create mode 100644 docs/modules/fileapp.txt create mode 100644 docs/modules/fixture.txt create mode 100644 docs/modules/gzipper.txt create mode 100644 docs/modules/httpexceptions.txt create mode 100644 docs/modules/httpheaders.txt create mode 100644 docs/modules/httpserver.txt create mode 100644 docs/modules/lint.txt create mode 100644 docs/modules/pony.txt create mode 100644 docs/modules/progress.txt create mode 100644 docs/modules/proxy.txt create mode 100644 docs/modules/recursive.txt create mode 100644 docs/modules/registry.txt create mode 100644 docs/modules/reloader.txt create mode 100644 docs/modules/request.txt create mode 100644 docs/modules/response.txt create mode 100644 docs/modules/session.txt create mode 100644 docs/modules/transaction.txt create mode 100644 docs/modules/translogger.txt create mode 100644 docs/modules/url.txt create mode 100644 docs/modules/urlmap.txt create mode 100644 docs/modules/urlparser.txt create mode 100644 docs/modules/util.import_string.txt create mode 100644 docs/modules/util.multidict.txt create mode 100644 docs/modules/wsgilib.txt create mode 100644 docs/modules/wsgiwrappers.txt create mode 100644 docs/news.txt create mode 100644 docs/paste-httpserver-threadpool.txt create mode 100644 docs/test_server.ini create mode 100644 docs/testing-applications.txt create mode 100644 docs/url-parsing-with-wsgi.txt create mode 100644 docs/web/default-site.css create mode 100644 docs/web/site.js create mode 100644 docs/web/style.css create mode 100644 paste/__init__.py create mode 100644 paste/auth/__init__.py create mode 100644 paste/auth/auth_tkt.py create mode 100644 paste/auth/basic.py create mode 100644 paste/auth/cas.py create mode 100644 paste/auth/cookie.py create mode 100644 paste/auth/digest.py create mode 100644 paste/auth/form.py create mode 100644 paste/auth/grantip.py create mode 100644 paste/auth/multi.py create mode 100644 paste/auth/open_id.py create mode 100644 paste/cascade.py create mode 100644 paste/cgiapp.py create mode 100644 paste/cgitb_catcher.py create mode 100644 paste/config.py create mode 100644 paste/cowbell/__init__.py create mode 100644 paste/cowbell/bell-ascending.png create mode 100644 paste/cowbell/bell-descending.png create mode 100644 paste/debug/__init__.py create mode 100755 paste/debug/debugapp.py create mode 100755 paste/debug/doctest_webapp.py create mode 100644 paste/debug/fsdiff.py create mode 100644 paste/debug/prints.py create mode 100644 paste/debug/profile.py create mode 100755 paste/debug/testserver.py create mode 100644 paste/debug/watchthreads.py create mode 100644 paste/debug/wdg_validate.py create mode 100644 paste/errordocument.py create mode 100644 paste/evalexception/__init__.py create mode 100644 paste/evalexception/evalcontext.py create mode 100644 paste/evalexception/media/MochiKit.packed.js create mode 100644 paste/evalexception/media/debug.js create mode 100644 paste/evalexception/media/minus.jpg create mode 100644 paste/evalexception/media/plus.jpg create mode 100644 paste/evalexception/middleware.py create mode 100644 paste/exceptions/__init__.py create mode 100644 paste/exceptions/collector.py create mode 100644 paste/exceptions/errormiddleware.py create mode 100644 paste/exceptions/formatter.py create mode 100644 paste/exceptions/reporter.py create mode 100644 paste/exceptions/serial_number_generator.py create mode 100644 paste/fileapp.py create mode 100644 paste/fixture.py create mode 100644 paste/flup_session.py create mode 100644 paste/gzipper.py create mode 100644 paste/httpexceptions.py create mode 100644 paste/httpheaders.py create mode 100755 paste/httpserver.py create mode 100644 paste/lint.py create mode 100644 paste/modpython.py create mode 100644 paste/pony.py create mode 100755 paste/progress.py create mode 100644 paste/proxy.py create mode 100644 paste/recursive.py create mode 100644 paste/registry.py create mode 100644 paste/reloader.py create mode 100644 paste/request.py create mode 100644 paste/response.py create mode 100644 paste/session.py create mode 100644 paste/transaction.py create mode 100644 paste/translogger.py create mode 100644 paste/url.py create mode 100644 paste/urlmap.py create mode 100644 paste/urlparser.py create mode 100644 paste/util/PySourceColor.py create mode 100644 paste/util/__init__.py create mode 100644 paste/util/classinit.py create mode 100644 paste/util/classinstance.py create mode 100644 paste/util/converters.py create mode 100644 paste/util/dateinterval.py create mode 100644 paste/util/datetimeutil.py create mode 100644 paste/util/filemixin.py create mode 100644 paste/util/finddata.py create mode 100644 paste/util/findpackage.py create mode 100644 paste/util/import_string.py create mode 100644 paste/util/intset.py create mode 100644 paste/util/ip4.py create mode 100644 paste/util/killthread.py create mode 100644 paste/util/looper.py create mode 100644 paste/util/mimeparse.py create mode 100644 paste/util/multidict.py create mode 100644 paste/util/quoting.py create mode 100644 paste/util/scgiserver.py create mode 100644 paste/util/template.py create mode 100644 paste/util/threadedprint.py create mode 100644 paste/util/threadinglocal.py create mode 100644 paste/wsgilib.py create mode 100644 paste/wsgiwrappers.py create mode 100755 regen-docs create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100755 tests/cgiapp_data/error.cgi create mode 100755 tests/cgiapp_data/form.cgi create mode 100755 tests/cgiapp_data/ok.cgi create mode 100755 tests/cgiapp_data/stderr.cgi create mode 100644 tests/test_auth/__init__.py create mode 100644 tests/test_auth/test_auth_cookie.py create mode 100644 tests/test_auth/test_auth_digest.py create mode 100644 tests/test_cgiapp.py create mode 100644 tests/test_cgitb_catcher.py create mode 100644 tests/test_config.py create mode 100644 tests/test_doctests.py create mode 100644 tests/test_errordocument.py create mode 100644 tests/test_exceptions/__init__.py create mode 100644 tests/test_exceptions/test_error_middleware.py create mode 100644 tests/test_exceptions/test_formatter.py create mode 100644 tests/test_exceptions/test_httpexceptions.py create mode 100644 tests/test_exceptions/test_reporter.py create mode 100644 tests/test_fileapp.py create mode 100644 tests/test_fixture.py create mode 100644 tests/test_grantip.py create mode 100644 tests/test_gzipper.py create mode 100644 tests/test_httpheaders.py create mode 100644 tests/test_httpserver.py create mode 100644 tests/test_import_string.py create mode 100644 tests/test_multidict.py create mode 100644 tests/test_profilemiddleware.py create mode 100644 tests/test_proxy.py create mode 100644 tests/test_recursive.py create mode 100644 tests/test_registry.py create mode 100644 tests/test_request.py create mode 100644 tests/test_request_form.py create mode 100644 tests/test_response.py create mode 100644 tests/test_session.py create mode 100644 tests/test_template.txt create mode 100644 tests/test_urlmap.py create mode 100644 tests/test_urlparser.py create mode 100644 tests/test_util/__init__.py create mode 100644 tests/test_util/test_datetimeutil.py create mode 100644 tests/test_util/test_mimeparse.py create mode 100644 tests/test_util/test_quoting.py create mode 100644 tests/test_wsgiwrappers.py create mode 100644 tests/urlparser_data/__init__.py create mode 100644 tests/urlparser_data/deep/index.html create mode 100644 tests/urlparser_data/deep/sub/Main.txt create mode 100644 tests/urlparser_data/find_file/dir with spaces/test 4.html create mode 100644 tests/urlparser_data/find_file/index.txt create mode 100644 tests/urlparser_data/find_file/test 3.html create mode 100644 tests/urlparser_data/find_file/test2.html create mode 100644 tests/urlparser_data/hook/__init__.py create mode 100644 tests/urlparser_data/hook/app.py create mode 100644 tests/urlparser_data/hook/index.py create mode 100644 tests/urlparser_data/not_found/__init__.py create mode 100644 tests/urlparser_data/not_found/recur/__init__.py create mode 100644 tests/urlparser_data/not_found/recur/isfound.txt create mode 100644 tests/urlparser_data/not_found/simple/__init__.py create mode 100644 tests/urlparser_data/not_found/simple/found.txt create mode 100644 tests/urlparser_data/not_found/user/__init__.py create mode 100644 tests/urlparser_data/not_found/user/list.py create mode 100644 tests/urlparser_data/python/__init__.py create mode 100644 tests/urlparser_data/python/simpleapp.py create mode 100644 tests/urlparser_data/python/stream.py create mode 100644 tests/urlparser_data/python/sub/__init__.py create mode 100644 tests/urlparser_data/python/sub/simpleapp.py create mode 100644 tests/urlparser_data/secured.txt create mode 100644 tox.ini diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..36cfdf7 --- /dev/null +++ b/.hgignore @@ -0,0 +1,14 @@ +syntax: glob + +.project +.pydevproject +.settings +*.pyc +*.pyo +*.log +*.tmp +*.egg-info +dist/ +build/ +docs/_build +.tox diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..cc3f4ba --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,10 @@ +recursive-include docs *.txt *.css *.js +include docs/_templates/*.html +include docs/conf.py +include docs/test_server.ini +include regen-docs +include tox.ini +recursive-exclude docs/_build/_sources * +recursive-include docs/_build *.html +recursive-include tests *.txt *.py *.cgi *.html +recursive-include paste *.js *.jpg *.png diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..3b2ab8f --- /dev/null +++ b/README.rst @@ -0,0 +1,109 @@ +Paste provides 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/dev/peps/pep-0333.html + +* `Paste project at Bitbucket (source code, bug tracker) + `_ +* `Paste on the Python Cheeseshop (PyPI) + `_ +* `Paste documentation + `_ + +See also: + +* `PasteDeploy `_ +* `PasteScript `_ +* `WebTest `_ +* `WebOb `_ + +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 + `_-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 + `_, 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 official development repo is at https://bitbucket.org/ianb/paste. + +For the latest changes see the `news file +`_. diff --git a/docs/DeveloperGuidelines.txt b/docs/DeveloperGuidelines.txt new file mode 100644 index 0000000..69f39ee --- /dev/null +++ b/docs/DeveloperGuidelines.txt @@ -0,0 +1,110 @@ +++++++++++++++++++++++++++++ +Python Paste Developer Guide +++++++++++++++++++++++++++++ + +Hi. Welcome to Paste. I hope you enjoy your stay here. + +I hope to bring together multiple efforts here, for Paste to support +multiple frameworks and directions, while presenting a fairly +integrated frontend to users. How to do that? That's an open +question, and this code is in some ways an exploration. + +There's some basic principles: + +* Keep stuff decoupled. + +* Must be testable. Of course tested is even better than testable. + +* Use WSGI standards for communication between decoupled libraries. + +* When possible, use HTTP semantics for communicating between + libraries (e.g., indicate cachability using the appropriate HTTP + headers). + +* When possible, use WSGI as a wrapper around web-neutral libraries. + For instance, the configuration is a simple library, but the WSGI + middleware that puts the configuration in place is really really + simple. If it could be used outside of a web context, then having + both a library and middleware form is good. + +* Entry into frameworks should be easy, but exit should also be easy. + Heterogeneous frameworks and applications are the ambition. But we + have to get some messiness into Paste before we can try to resolve + that messiness. + +* When all is said and done, users should be able to ignore much of + what we've done and focus on writing their applications, and Stuff + Just Works. Documentation is good; stuff that works without user + intervention is better. + +Developer Info +============== + +Mostly, if there's a problem we can discuss it and work it out, no one +is going to bite your head off for committing something. + +* Framework-like things should go in subpackages, or perhaps in + separate distributions entirely (Paste WebKit and Wareweb were + extracted for this reason). + +* Integrating external servers and frameworks is also interesting, but + it's best to introduce that as a requirement instead of including + the work here. Paste Script contains several wrappers for external + projects (servers in particular). + +* Tests are good. We use py.test_, because it is simple. I want to + use doctests too, but the infrastructure isn't really there now -- + but please feel free to use those too. ``unittest`` is kind of + annoying, and py.test is both more powerful and easier to write for. + Tests should go in the ``tests/`` directory. ``paste.fixture`` + contains some convenience functions for testing WSGI applications + and middleware. Pay particular attention to ``TestApp``. + +.. _py.test: http://codespeak.net/py/current/doc/test.html + +* If you move something around that someone may be using, keep their + imports working and introduce a warning, like:: + + def backward_compat_function(*args, **kw): + import warnings + # Deprecated on 2005 Mar 5 + warnings.warn('Moved to foo.function', DeprecationWarning, 2) + return foo.function(*args, **kw) + +* If something is really experimental, put it in your home directory, + or make a branch in your home directory. You can make a home + directory for yourself, in ``http://svn.w4py.org/home/username``. + +* Not everything in the repository or even in the trunk will + necessarily go into the release. The release should contain stuff + that is tested, documented, and useful. Each module or feature also + needs a champion -- someone who will stand by the code, answer + questions, etc. It doesn't have to be the original developer, but + there has to be *someone*. So when a release is cut, if some + modules don't fulfill that they may be left out. + +* Try to keep to the `Style Guidelines`_. But if you are bringing in + outside work, don't stress out too much about it. Still, if you + have a choice, follow that. Those guidelines are meant to represent + conventional Python style guides, there's nothing out of the normal + there. + +.. _Style Guidelines: StyleGuide.html + +* Write your docstrings in `restructured text + `_. As time goes on, I + want to rely on docstrings more for documentation, with shorter + narrative documentation pointing into the documentation generated + from docstrings. + + The generation is done with `Pudge `_. + To try generating the documentation, this should work:: + + $ easy_install svn://lesscode.org/buildutils/trunk \ + svn://lesscode.org/pudge/trunk + $ cd Paste + $ python setup.py pudge + + This will install Pudge and `buildutils + `_, and then generate the + documentation into ``Paste/docs/html/``. diff --git a/docs/StyleGuide.txt b/docs/StyleGuide.txt new file mode 100644 index 0000000..b307282 --- /dev/null +++ b/docs/StyleGuide.txt @@ -0,0 +1,102 @@ ++++++++++++++++++++ +Paste Style Guide ++++++++++++++++++++ + +Generally you should follow the recommendations in `PEP 8`_, the +Python Style Guide. Some things to take particular note of: + +.. _PEP 8: http://www.python.org/peps/pep-0008.html + +* **No tabs**. Not anywhere. Always indent with 4 spaces. + +* I don't stress too much on line length. But try to break lines up + by grouping with parenthesis instead of with backslashes (if you + can). Do asserts like:: + + assert some_condition(a, b), ( + "Some condition failed, %r isn't right!" % a) + +* But if you are having problems with line length, maybe you should + just break the expression up into multiple statements. + +* Blank lines between methods, unless they are very small and closely + bound to each other. + +* Don't use the form ``condition and trueValue or falseValue``. Break + it out and use a variable. + +* I (Ian Bicking) am very picky about whitespace. There's one and + only one right way to do it. Good examples:: + + short = 3 + longerVar = 4 + + if x == 4: + do stuff + + func(arg1='a', arg2='b') + func((a + b)*10) + + **Bad** examples:: + + short =3 + longerVar=4 + + if x==4: do stuff + + func(arg1 = 'a', arg2 = 'b') + func(a,b) + func( a, b ) + [ 1, 2, 3 ] + + If the whitespace isn't right, it'll annoy me and I'll feel a need + to fix it. Really, this whitespace stuff shouldn't be that + controversial should it? Some particular points that I feel + strongly about: + + * No space after a function name (bad: ``func (arg)``). + * No space after or before a parenthesis (bad: ``func( arg )``). + * Always one space after a comma (bad: ``func(a,b)``). + +* Use ``@@`` to mark something that is suboptimal, or where you have a + concern that it's not right. Try to also date it and put your + username there. + +* Docstrings are good. They should look like:: + + class AClass(object): + """ + doc string... + """ + + Don't use single quotes (''') -- they tend to cause problems in + Emacs. Don't bother trying make the string less vertically compact. + +* Comments go right before the thing they are commenting on. + +* Methods never, ever, ever start with capital letters. Generally + only classes are capitalized. But definitely never methods. + +* Use ``cls`` to refer to a class. Use ``meta`` to refer to a + metaclass (which also happens to be a class, but calling a metaclass + ``cls`` will be confusing). + +* Use ``isinstance`` instead of comparing types. E.g.:: + + if isinstance(var, str): ... + # Bad: + if type(var) is StringType: ... + +* Never, ever use two leading underscores. This is annoyingly + private. If name clashes are a concern, use explicit name mangling + instead (e.g., ``_MyThing_blahblah``). This is essentially the same + thing as double-underscore, only it's transparent where double + underscore obscures. + +* Module names should be unique in the package. Subpackages shouldn't + share module names with sibling or parent packages. Sadly this + isn't possible for ``__init__.py``, but it's otherwise easy enough. + +* Module names should be all lower case, and probably have no + underscores (smushedwords). + diff --git a/docs/_static/paste.css b/docs/_static/paste.css new file mode 100644 index 0000000..6705e5d --- /dev/null +++ b/docs/_static/paste.css @@ -0,0 +1,15 @@ +a.invisible-link { + color: #fff; + text-decoration: none; +} + +a.invisible-link:visited { + color: #fff; + text-decoration: none; +} + +a.invisible:link { + color: #fff; + text-decoration: none; +} + diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 0000000..6ae2d42 --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,29 @@ +{% extends "!layout.html" %} + +{% block extrahead %} +{{ super() }} + +{% endblock %} + +{% block sidebartoc %} +

Python Paste

+ + + +{{ super() }} +{% endblock %} diff --git a/docs/community/index.txt b/docs/community/index.txt new file mode 100644 index 0000000..5b30110 --- /dev/null +++ b/docs/community/index.txt @@ -0,0 +1,15 @@ +Community +========= + +Much of the communication goes on in the `mailing lists +`_; see that page for information on the lists. + +For live IRC discussion, try the ``#pythonpaste`` channel on `Freenode +`_. + +If you find bugs in the code or documentation, please `submit a ticket +`_. You can also `view tickets +`_. + + + diff --git a/docs/community/mailing-list.txt b/docs/community/mailing-list.txt new file mode 100644 index 0000000..854ec3e --- /dev/null +++ b/docs/community/mailing-list.txt @@ -0,0 +1,14 @@ +Mailing Lists +============= + +General discussion and questions should go to: + +`paste-users@googlegroups.org `_: + New posts are `on Google Groups `_ `old posts are in their own archive `_ + +More abstract discussion of Python web programming should go to: + +`web-sig@python.org `_: + `Subscribe `__, + `Archives `__ + diff --git a/docs/community/repository.txt b/docs/community/repository.txt new file mode 100644 index 0000000..b8f3700 --- /dev/null +++ b/docs/community/repository.txt @@ -0,0 +1,10 @@ +Repository +========== + +Paste is kept in a Mercurial (hg) repository at +http://bitbucket.org/ianb/paste + +If you are using a command-line Mercurial client, you can check +it out like:: + + hg clone http://bitbucket.org/ianb/paste diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..e035d50 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# +# Paste documentation build configuration file, created by +# sphinx-quickstart on Tue Apr 22 22:08:49 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default value; values that are commented out +# serve to show the default value. + +import sys + +# If your extensions are in another directory, add it here. +#sys.path.append('some/directory') + +# General configuration +# --------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.txt' + +# The master toctree document. +master_doc = 'index' + +# General substitutions. +project = 'Paste' +copyright = '2008, Ian Bicking' + +# The default replacements for |version| and |release|, also used in various +# other places throughout the built documents. +# +# The short X.Y version. +version = '1.7' +# The full version, including alpha/beta/rc tags. +release = '1.7.5.1' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +unused_docs = ['include/contact.txt', 'include/reference_header.txt'] + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# Options for HTML output +# ----------------------- + +# The style sheet to use for HTML and HTML Help pages. A file of that name +# must exist either in Sphinx' static/ path, or in one of the custom paths +# given in html_static_path. +html_style = 'default.css' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Content template for the index page. +#html_index = '' + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If true, the reST sources are included in the HTML build as _sources/. +#html_copy_source = True + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Pastedoc' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +#latex_documents = [] + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/docs/default.css b/docs/default.css new file mode 100644 index 0000000..a570fb6 --- /dev/null +++ b/docs/default.css @@ -0,0 +1,394 @@ +/* +:Author: David Goodger, Ian Bicking +:Contact: ianb@colorstudy.com +:date: $Date: 2003/11/01 20:35:45 $ +:version: $Revision: 1.3 $ +:copyright: This stylesheet has been placed in the public domain. + +A modification of the default cascading style sheet (v.1.3) for the +HTML output of Docutils. +*/ + +body { + font-family: Arial, sans-serif; + background-color: #fff; +} + +em, i { + /* Typically serif fonts have much nicer italics */ + font-family: Times New Roman, Times, serif; +} + +li { + list-style-type: circle; +} + +a.target { + color: blue; +} + +a.toc-backref { + text-decoration: none; + color: black; +} + +a.toc-backref:hover { + background-color: inherit; +} + +a:hover { + background-color: #ccc; +} + +h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6:hover { + background-color: inherit; +} + +cite { + font-style: normal; + font-family: monospace; + font-weight: bold; +} + +dd { + margin-bottom: 0.5em; +} + +div.abstract { + margin: 2em 5em; +} + +div.abstract p.topic-title { + font-weight: bold; + text-align: center; +} + +div.attention, div.caution, div.danger, div.error, div.hint, +div.important, div.note, div.tip, div.warning { + background-color: #ccc; + width: 40%; + border: medium outset; + padding: 3px; + float: right +} + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title { + color: #c00; + font-weight: bold; + font-family: sans-serif; + text-align: center; + background-color: #999; + display: block; + margin: 0; +} + +div.hint p.admonition-title, div.important p.admonition-title, +div.note p.admonition-title, div.tip p.admonition-title { + font-weight: bold; + font-family: sans-serif; + text-align: center; + background-color: #999; + display: block; + margin: 0; +} + +div.dedication { + margin: 2em 5em; + text-align: center; + font-style: italic; +} + +div.dedication p.topic-title { + font-weight: bold; + font-style: normal; +} + +div.figure { + margin-left: 2em; +} + +div.footer, div.header { + font-size: smaller; +} + +div.system-messages { + margin: 5em; +} + +div.system-messages h1 { + color: red; +} + +div.system-message { + border: medium outset; + padding: 1em; +} + +div.system-message p.system-message-title { + color: red; + font-weight: bold; +} + +div.topic { + margin: 2em; +} + +h1, h2, h3, h4, h5, h6 { + font-family: Helvetica, Arial, sans-serif; + border: thin solid black; + /* This makes the borders rounded on Mozilla, which pleases me */ + -moz-border-radius: 8px; + padding: 4px; +} + +h1 { + background-color: #449; + color: #fff; + border: medium solid black; +} + +h1 a.toc-backref, h2 a.toc-backref { + color: #fff; +} + +h2 { + background-color: #666; + color: #fff; + border: medium solid black; +} + +h3, h4, h5, h6 { + background-color: #ccc; + color: #000; +} + +h3 a.toc-backref, h4 a.toc-backref, h5 a.toc-backref, +h6 a.toc-backref { + color: #000; +} + +h1.title { + text-align: center; + background-color: #449; + color: #fff; + border: thick solid black; + -moz-border-radius: 20px; +} + +h2.subtitle { + text-align: center; +} + +hr { + width: 75%; +} + +ol.simple, ul.simple { + margin-bottom: 1em; +} + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +p.caption { + font-style: italic; +} + +p.credits { + font-style: italic; + font-size: smaller; +} + +p.first { + margin-top: 0; +} + +p.label { + white-space: nowrap; +} + +p.topic-title { + font-weight: bold; +} + +pre.address { + margin-bottom: 0; + margin-top: 0; + font-family: serif; + font-size: 100%; +} + +pre.line-block { + font-family: serif; + font-size: 100%; +} + +pre.literal-block, pre.doctest-block { + margin-left: 2em; + margin-right: 2em; + background-color: #eee; + border: thin black solid; + padding: 5px; +} + +span.classifier { + font-family: sans-serif; + font-style: oblique; +} + +span.classifier-delimiter { + font-family: sans-serif; + font-weight: bold; +} + +span.interpreted { + font-family: sans-serif; +} + +span.option-argument { + font-style: italic; +} + +span.pre { + white-space: pre; +} + +span.problematic { + color: red; +} + +table { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +table.citation { + border-left: solid thin gray; + padding-left: 0.5ex +} + +table.docinfo { + margin: 2em 4em; +} + +table.footnote { + border-left: solid thin black; + padding-left: 0.5ex; +} + +td, th { + padding-left: 0.5em; + padding-right: 0.5em; + vertical-align: top; +} + +td > p:first-child, th > p:first-child { + margin-top: 0em; +} + +th.docinfo-name, th.field-name { + font-weight: bold; + text-align: left; + white-space: nowrap; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + font-size: 100%; +} + +code, tt { + color: #006; +} + +ul.auto-toc { + list-style-type: none; +} + +/***************************************** + * Doctest embedded examples + *****************************************/ + +span.doctest-url { + background-color: #eee; + border-top: 2px outset #666; + border-left: 2px outset #666; + border-right: 2px outset #666; + padding: 0.25em; +} + +div.doctest-example { + border: outset 5px #666; + background-color: #eee; + font-family: default; + padding: 0.5em; +} + +div.doctest-example h1 { + background-color: inherit; + border: none; + color: inherit; + font-family: default; +} + +div.doctest-example tt { + color: inherit; +} + +div.doctest-status { + background-color: #060; + color: #fff; +} + +span.doctest-header { + background-color: #ccc; + font-family: monospace; +} + +pre.doctest-errors { + border: none; + background-color: #333; + color: #600; +} + +div.source-code { + background-color: #000; + border: inset #999 3px; + overflow: auto; +} + +pre.source-code { + background-color: #000; + border: inset #999 3px; + overflow: auto; + font-family: monospace; + color: #fff; +} + +span.source-filename { + background-color: #000; + border-top: 2px outset #999; + border-left: 2px outset #999; + border-right: 2px outset #999; + padding: 0.25em; + color: #fff +} + diff --git a/docs/developer-features.txt b/docs/developer-features.txt new file mode 100644 index 0000000..503d419 --- /dev/null +++ b/docs/developer-features.txt @@ -0,0 +1,148 @@ +Features +======== + +Testing +------- + +* A fixture for testing WSGI applications conveniently and in-process, + in :class:`paste.fixture.TestApp` + +* A fixture for testing command-line applications, also in + :class:`paste.fixture.TestFileEnvironment` + +* Check components for WSGI-compliance in :mod:`paste.lint` + +* Check filesystem changes, with :mod:`paste.debug.fsdiff` + +Server +------ + +* A threaded HTTP server in :mod:`paste.httpserver` + +* A tool for seeing and killing errant threads in the HTTP server, in + :mod:`paste.debug.watchthreads` + +Dispatching +----------- + +* Chain and cascade WSGI applications (returning the first non-error + response) in :mod:`paste.cascade` + +* Dispatch to several WSGI applications based on URL prefixes, in + :mod:`paste.urlmap` + +* Allow applications to make subrequests and forward requests + internally, in :mod:`paste.recursive` + +* Redirect error pages (e.g., 404 Not Found) to custom error pages, in + :mod:`paste.errordocument`. + +Web Application +--------------- + +* Easily deal with incoming requests and sending a response in + :mod:`paste.wsgiwrappers` + +* Work directly with the WSGI environment in :mod:`paste.request` + +* Run CGI programs as WSGI applications in :mod:`paste.cgiapp` + +* Traverse files and load WSGI applications from ``.py`` files (or + static files), in :mod:`paste.urlparser` + +* Serve static directories of files, also in :mod:`paste.urlparser`; also + serve using the Setuptools ``pkg_resources`` resource API. + +* Proxy to other servers, treating external HTTP servers as WSGI + applications, in :mod:`paste.proxy`. + +* Serve files (with support for ``If-Modified-Since``, etc) in + :mod:`paste.fileapp` + +Tools +----- + +* Catch HTTP-related exceptions (e.g., ``HTTPNotFound``) and turn them + into proper responses in :mod:`paste.httpexceptions` + +* Manage HTTP header fields with :mod:`paste.httpheaders` + +* Handle authentication/identification of requests in :mod:`paste.auth` + +* Create sessions in :mod:`paste.session` and :mod:`paste.flup_session` + +* Gzip responses in :mod:`paste.gzipper` + +* A wide variety of routines for manipulating WSGI requests and + producing responses, in :mod:`paste.request`, :mod:`paste.response` and + :mod:`paste.wsgilib`. + +* Create Apache-style logs in :mod:`paste.translogger` + +* Handy request and response wrappers in :mod:`paste.wsgiwrappers` + +* Handling of request-local module globals sanely in :mod:`paste.registry` + +Authentication +-------------- + +* Authentication using cookies in :mod:`paste.auth.cookie` and + :mod:`paste.auth.auth_tkt`; login form in :mod:`paste.auth.form` + +* Authentication using `OpenID `_ in + :mod:`paste.auth.open_id`, using `CAS + `_ in :mod:`paste.auth.cas` + +* HTTP authentication in :mod:`paste.auth.basic` and + :mod:`paste.auth.digest` + +* Dispatch to different authentication methods based on User-Agent, in + :mod:`paste.auth.multi` + +* Grant roles based on IP addresses, in :mod:`paste.auth.grantip` + +Debugging Filters +----------------- + +* Catch (optionally email) errors with extended tracebacks (using + Zope/ZPT conventions) in :mod:`paste.exceptions` + +* During debugging, show tracebacks with information about each stack + frame, including an interactive prompt that runs in the individual + stack frames, in :mod:`paste.evalexception`. + +* Catch errors presenting a `cgitb + `_-based + output, in :mod:`paste.cgitb_catcher`. + +* Profile each request and append profiling information to the HTML, + in :mod:`paste.debug.profile` + +* Capture ``print`` output and present it in the browser for + debugging, in :mod:`paste.debug.prints` + +* Validate all HTML output from applications using the `WDG Validator + `_, appending any errors + or warnings to the page, in :mod:`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 + :mod:`paste.reloader` + +* A class for generating and traversing URLs, and creating associated + HTML code, in :mod:`paste.url` + +* A small templating language (for internal use) in + :mod:`paste.util.template` + +* A class to help with loops in templates, in :mod:`paste.util.looper` + +* Import modules and objects given a string, in + :mod:`paste.util.import_string` + +* Ordered dictionary that can have multiple values with the same key, + in :mod:`paste.util.multidict` + diff --git a/docs/do-it-yourself-framework.txt b/docs/do-it-yourself-framework.txt new file mode 100644 index 0000000..ae77ec0 --- /dev/null +++ b/docs/do-it-yourself-framework.txt @@ -0,0 +1,538 @@ +A Do-It-Yourself Framework +++++++++++++++++++++++++++ + +:author: Ian Bicking +:revision: $Rev$ +:date: $LastChangedDate$ + +This tutorial has been translated `into Portuguese +`_. + +A newer version of this article is available `using WebOb +`_. + +.. contents:: + +.. comments: + + Explain SCRIPT_NAME/PATH_INFO better + +Introduction and Audience +========================= + +This short tutorial is meant to teach you a little about WSGI, and as +an example a bit about the architecture that Paste has enabled and +encourages. + +This isn't an introduction to all the parts of Paste -- in fact, we'll +only use a few, and explain each part. This isn't to encourage +everyone to go off and make their own framework (though honestly I +wouldn't mind). The goal is that when you have finished reading this +you feel more comfortable with some of the frameworks built using this +architecture, and a little more secure that you will understand the +internals if you look under the hood. + +What is WSGI? +============= + +At its simplest WSGI is an interface between web servers and web +applications. We'll explain the mechanics of WSGI below, but a higher +level view is to say that WSGI lets code pass around web requests in a +fairly formal way. But there's more! WSGI is more than just HTTP. +It might seem like it is just *barely* more than HTTP, but that little +bit is important: + +* You pass around a CGI-like environment, which means data like + ``REMOTE_USER`` (the logged-in username) can be securely passed + about. + +* A CGI-like environment can be passed around with more context -- + specifically instead of just one path you two: ``SCRIPT_NAME`` (how + we got here) and ``PATH_INFO`` (what we have left). + +* You can -- and often should -- put your own extensions into the WSGI + environment. This allows for callbacks, extra information, + arbitrary Python objects, or whatever you want. These are things + you can't put in custom HTTP headers. + +This means that WSGI can be used not just between a web server an an +application, but can be used at all levels for communication. This +allows web applications to become more like libraries -- well +encapsulated and reusable, but still with rich reusable functionality. + +Writing a WSGI Application +========================== + +The first part is about how to use `WSGI +`_ at its most basic. You +can read the spec, but I'll do a very brief summary: + +* You will be writing a *WSGI application*. That's an object that + responds to requests. An application is just a callable object + (like a function) that takes two arguments: ``environ`` and + ``start_response``. + +* The environment looks a lot like a CGI environment, with keys like + ``REQUEST_METHOD``, ``HTTP_HOST``, etc. + +* The environment also has some special keys like ``wsgi.input`` (the + input stream, like the body of a POST request). + +* ``start_response`` is a function that starts the response -- you + give the status and headers here. + +* Lastly the application returns an iterator with the body response + (commonly this is just a list of strings, or just a list containing + one string that is the entire body.) + +So, here's a simple application:: + + def app(environ, start_response): + start_response('200 OK', [('content-type', 'text/html')]) + return ['Hello world!'] + +Well... that's unsatisfying. Sure, you can imagine what it does, but +you can't exactly point your web browser at it. + +There's other cleaner ways to do this, but this tutorial isn't about +*clean* it's about *easy-to-understand*. So just add this to the +bottom of your file:: + + if __name__ == '__main__': + from paste import httpserver + httpserver.serve(app, host='127.0.0.1', port='8080') + +Now visit http://localhost:8080 and you should see your new app. +If you want to understand how a WSGI server works, I'd recommend +looking at the `CGI WSGI server +`_ +in the WSGI spec. + +An Interactive App +------------------ + +That last app wasn't very interesting. Let's at least make it +interactive. To do that we'll give a form, and then parse the form +fields:: + + from paste.request import parse_formvars + + def app(environ, start_response): + fields = parse_formvars(environ) + if environ['REQUEST_METHOD'] == 'POST': + start_response('200 OK', [('content-type', 'text/html')]) + return ['Hello, ', fields['name'], '!'] + else: + start_response('200 OK', [('content-type', 'text/html')]) + return ['
Name:
'] + +The ``parse_formvars`` function just takes the WSGI environment and +calls the `cgi `_ +module (the ``FieldStorage`` class) and turns that into a MultiDict. + +Now For a Framework +=================== + +Now, this probably feels a bit crude. After all, we're testing for +things like REQUEST_METHOD to handle more than one thing, and it's +unclear how you can have more than one page. + +We want to build a framework, which is just a kind of generic +application. In this tutorial we'll implement an *object publisher*, +which is something you may have seen in Zope, Quixote, or CherryPy. + +Object Publishing +----------------- + +In a typical Python object publisher you translate ``/`` to ``.``. So +``/articles/view?id=5`` turns into ``root.articles.view(id=5)``. We +have to start with some root object, of course, which we'll pass in... + +:: + + class ObjectPublisher(object): + + def __init__(self, root): + self.root = root + + def __call__(self, environ, start_response): + ... + + app = ObjectPublisher(my_root_object) + +We override ``__call__`` to make instances of ``ObjectPublisher`` +callable objects, just like a function, and just like WSGI +applications. Now all we have to do is translate that ``environ`` +into the thing we are publishing, then call that thing, then turn the +response into what WSGI wants. + +The Path +-------- + +WSGI puts the requested path into two variables: ``SCRIPT_NAME`` and +``PATH_INFO``. ``SCRIPT_NAME`` is everything that was used up +*getting here*. ``PATH_INFO`` is everything left over -- it's +the part the framework should be using to find the object. If you put +the two back together, you get the full path used to get to where we +are right now; this is very useful for generating correct URLs, and +we'll make sure we preserve this. + +So here's how we might implement ``__call__``:: + + def __call__(self, environ, start_response): + fields = parse_formvars(environ) + obj = self.find_object(self.root, environ) + response_body = obj(**fields.mixed()) + start_response('200 OK', [('content-type', 'text/html')]) + return [response_body] + + def find_object(self, obj, environ): + path_info = environ.get('PATH_INFO', '') + if not path_info or path_info == '/': + # We've arrived! + return obj + # PATH_INFO always starts with a /, so we'll get rid of it: + path_info = path_info.lstrip('/') + # Then split the path into the "next" chunk, and everything + # after it ("rest"): + parts = path_info.split('/', 1) + next = parts[0] + if len(parts) == 1: + rest = '' + else: + rest = '/' + parts[1] + # Hide private methods/attributes: + assert not next.startswith('_') + # Now we get the attribute; getattr(a, 'b') is equivalent + # to a.b... + next_obj = getattr(obj, next) + # Now fix up SCRIPT_NAME and PATH_INFO... + environ['SCRIPT_NAME'] += '/' + next + environ['PATH_INFO'] = rest + # and now parse the remaining part of the URL... + return self.find_object(next_obj, environ) + +And that's it, we've got a framework. + +Taking It For a Ride +-------------------- + +Now, let's write a little application. Put that ``ObjectPublisher`` +class into a module ``objectpub``:: + + from objectpub import ObjectPublisher + + class Root(object): + + # The "index" method: + def __call__(self): + return ''' +
+ Name: + +
+ ''' + + def welcome(self, name): + return 'Hello %s!' % name + + app = ObjectPublisher(Root()) + + if __name__ == '__main__': + from paste import httpserver + httpserver.serve(app, host='127.0.0.1', port='8080') + +Alright, done! Oh, wait. There's still some big missing features, +like how do you set headers? And instead of giving ``404 Not Found`` +responses in some places, you'll just get an attribute error. We'll +fix those up in a later installment... + +Give Me More! +------------- + +You'll notice some things are missing here. Most specifically, +there's no way to set the output headers, and the information on the +request is a little slim. + +:: + + # This is just a dictionary-like object that has case- + # insensitive keys: + from paste.response import HeaderDict + + class Request(object): + def __init__(self, environ): + self.environ = environ + self.fields = parse_formvars(environ) + + class Response(object): + def __init__(self): + self.headers = HeaderDict( + {'content-type': 'text/html'}) + +Now I'll teach you a little trick. We don't want to change the +signature of the methods. But we can't put the request and response +objects in normal global variables, because we want to be +thread-friendly, and all threads see the same global variables (even +if they are processing different requests). + +But Python 2.4 introduced a concept of "thread-local values". That's +a value that just this one thread can see. This is in the +`threading.local `_ +object. When you create an instance of ``local`` any attributes you +set on that object can only be seen by the thread you set them in. So +we'll attach the request and response objects here. + +So, let's remind ourselves of what the ``__call__`` function looked +like:: + + class ObjectPublisher(object): + ... + + def __call__(self, environ, start_response): + fields = parse_formvars(environ) + obj = self.find_object(self.root, environ) + response_body = obj(**fields.mixed()) + start_response('200 OK', [('content-type', 'text/html')]) + return [response_body] + +Lets's update that:: + + import threading + webinfo = threading.local() + + class ObjectPublisher(object): + ... + + def __call__(self, environ, start_response): + webinfo.request = Request(environ) + webinfo.response = Response() + obj = self.find_object(self.root, environ) + response_body = obj(**dict(webinfo.request.fields)) + start_response('200 OK', webinfo.response.headers.items()) + return [response_body] + +Now in our method we might do:: + + class Root: + def rss(self): + webinfo.response.headers['content-type'] = 'text/xml' + ... + +If we were being fancier we would do things like handle `cookies +`_ in these +objects. But we aren't going to do that now. You have a framework, +be happy! + +WSGI Middleware +=============== + +`Middleware +`_ +is where people get a little intimidated by WSGI and Paste. + +What is middleware? Middleware is software that serves as an +intermediary. + + +So lets +write one. We'll write an authentication middleware, so that you can +keep your greeting from being seen by just anyone. + +Let's use HTTP authentication, which also can mystify people a bit. +HTTP authentication is fairly simple: + +* When authentication is requires, we give a ``401 Authentication + Required`` status with a ``WWW-Authenticate: Basic realm="This + Realm"`` header + +* The client then sends back a header ``Authorization: Basic + encoded_info`` + +* The "encoded_info" is a base-64 encoded version of + ``username:password`` + +So how does this work? Well, we're writing "middleware", which means +we'll typically pass the request on to another application. We could +change the request, or change the response, but in this case sometimes +we *won't* pass the request on (like, when we need to give that 401 +response). + +To give an example of a really really simple middleware, here's one +that capitalizes the response:: + + class Capitalizer(object): + + # We generally pass in the application to be wrapped to + # the middleware constructor: + def __init__(self, wrap_app): + self.wrap_app = wrap_app + + def __call__(self, environ, start_response): + # We call the application we are wrapping with the + # same arguments we get... + response_iter = self.wrap_app(environ, start_response) + # then change the response... + response_string = ''.join(response_iter) + return [response_string.upper()] + +Techically this isn't quite right, because there there's two ways to +return the response body, but we're skimming bits. +`paste.wsgilib.intercept_output +`_ +is a somewhat more thorough implementation of this. + +.. note:: + + This, like a lot of parts of this (now fairly old) tutorial is + better, more thorough, and easier using `WebOb + `_. This particular example looks + like:: + + from webob import Request + + class Capitalizer(object): + def __init__(self, app): + self.app = app + def __call__(self, environ, start_response): + req = Request(environ) + resp = req.get_response(self.app) + resp.body = resp.body.upper() + return resp(environ, start_response) + +So here's some code that does something useful, authentication:: + + class AuthMiddleware(object): + + def __init__(self, wrap_app): + self.wrap_app = wrap_app + + def __call__(self, environ, start_response): + if not self.authorized(environ.get('HTTP_AUTHORIZATION')): + # Essentially self.auth_required is a WSGI application + # that only knows how to respond with 401... + return self.auth_required(environ, start_response) + # But if everything is okay, then pass everything through + # to the application we are wrapping... + return self.wrap_app(environ, start_response) + + def authorized(self, auth_header): + if not auth_header: + # If they didn't give a header, they better login... + return False + # .split(None, 1) means split in two parts on whitespace: + auth_type, encoded_info = auth_header.split(None, 1) + assert auth_type.lower() == 'basic' + unencoded_info = encoded_info.decode('base64') + username, password = unencoded_info.split(':', 1) + return self.check_password(username, password) + + def check_password(self, username, password): + # Not very high security authentication... + return username == password + + def auth_required(self, environ, start_response): + start_response('401 Authentication Required', + [('Content-type', 'text/html'), + ('WWW-Authenticate', 'Basic realm="this realm"')]) + return [""" + + Authentication Required + +

Authentication Required

+ If you can't get in, then stay out. + + """] + +.. note:: + + Again, here's the same thing with WebOb:: + + from webob import Request, Response + + class AuthMiddleware(object): + def __init__(self, app): + self.app = app + def __call__(self, environ, start_response): + req = Request(environ) + if not self.authorized(req.headers['authorization']): + resp = self.auth_required(req) + else: + resp = self.app + return resp(environ, start_response) + def authorized(self, header): + if not header: + return False + auth_type, encoded = header.split(None, 1) + if not auth_type.lower() == 'basic': + return False + username, password = encoded.decode('base64').split(':', 1) + return self.check_password(username, password) + def check_password(self, username, password): + return username == password + def auth_required(self, req): + return Response(status=401, headers={'WWW-Authenticate': 'Basic realm="this realm"'}, + body="""\ + + Authentication Required + +

Authentication Required

+ If you can't get in, then stay out. + + """) + +So, how do we use this? + +:: + + app = ObjectPublisher(Root()) + wrapped_app = AuthMiddleware(app) + + if __name__ == '__main__': + from paste import httpserver + httpserver.serve(wrapped_app, host='127.0.0.1', port='8080') + +Now you have middleware! Hurrah! + +Give Me More Middleware! +------------------------ + +It's even easier to use other people's middleware than to make your +own, because then you don't have to program. If you've been following +along, you've probably encountered a few exceptions, and have to look +at the console to see the exception reports. Let's make that a little +easier, and show the exceptions in the browser... + +:: + + app = ObjectPublisher(Root()) + wrapped_app = AuthMiddleware(app) + from paste.exceptions.errormiddleware import ErrorMiddleware + exc_wrapped_app = ErrorMiddleware(wrapped_app) + +Easy! But let's make it *more* fancy... + +:: + + app = ObjectPublisher(Root()) + wrapped_app = AuthMiddleware(app) + from paste.evalexception import EvalException + exc_wrapped_app = EvalException(wrapped_app) + +So go make an error now. And hit the little +'s. And type stuff in +to the boxes. + +Conclusion +========== + +Now that you've created your framework and application (I'm sure it's +much nicer than the one I've given so far). You might keep writing it +(many people have so far), but even if you don't you should be able to +recognize these components in other frameworks now, and you'll have a +better understanding how they probably work under the covers. + +Also check out the version of this tutorial written `using WebOb +`_. That tutorial +includes things like **testing** and **pattern-matching dispatch** +(instead of object publishing). diff --git a/docs/download/index.txt b/docs/download/index.txt new file mode 100644 index 0000000..01f918f --- /dev/null +++ b/docs/download/index.txt @@ -0,0 +1,32 @@ +Downloads +========= + +Each of these packages is available in several formats. The source +distribution is a complete set of documentation, tests, and the source +files themselves. There are also two "Egg" files: these are files +`easy_install `_ +can install directly into your ``site-packages/`` directory, and are +Python-version specific. The download files for the latest version +are always located on the Cheese Shop pages (listed below). + +* `Paste `_ +* `Paste Script `_ +* `Paste Deploy `_ +* `Paste WebKit `_ +* `Wareweb `_ (deprecated, use `Pylons + `_ instead) + +All the packages are available in the Mercurial repositories rooted in +http://bitbucket.org/ianb/ + +* http://bitbucket.org/ianb/paste +* http://bitbucket.org/ianb/pastescript +* http://bitbucket.org/ianb/pastedeploy +* https://github.com/Pylons/webob +* ... and others + +Use:: + + hg clone http://bitbucket.org/ianb/paste + +to check out a working copy of Paste. diff --git a/docs/future.txt b/docs/future.txt new file mode 100644 index 0000000..697c750 --- /dev/null +++ b/docs/future.txt @@ -0,0 +1,108 @@ +The Future Of Paste +=================== + +Introduction +------------ + +Paste has been under development for a while, and has lots of code in it. Too much code! The code is largely decoupled except for some core functions shared by many parts of the code. Those core functions are largely replaced in `WebOb `_, and replaced with better implementations. + +The future of these pieces is to split them into independent packages, and refactor the internal Paste dependencies to rely instead on WebOb. + +Already Extracted +----------------- + +paste.fixture: + WebTest + ScriptTest + +paste.lint: + wsgiref.validate + +paste.exceptions and paste.evalexception: + WebError + +paste.util.template: + Tempita + + +To Be Separated +--------------- + +paste.httpserver and paste.debug.watchthreads: + Not sure what to call this. + +paste.cascade and paste.errordocuments: + Not sure; Ben has an implementation of errordocuments in ``pylons.middleware.StatusCodeRedirect`` + +paste.urlmap, paste.deploy.config.PrefixMiddleware: + In... some routing thing? Together with the previous package? + +paste.proxy: + WSGIProxy (needs lots of cleanup though) + +paste.fileapp, paste.urlparser.StaticURLParser, paste.urlparser.PkgResourcesParser: + In some new file-serving package. + +paste.cgiapp, wphp.fcgi_app: + Some proxyish app... maybe WSGIProxy? + +paste.translogger, paste.debug.prints, paste.util.threadedprint, wsgifilter.proxyapp.DebugHeaders: + Some... other place. Something loggy. + +paste.registry, paste.config: + Not sure. Alberto Valverde expressed interest in splitting out paste.registry. + +paste.cgitb_catcher: + Move to WebError? Not sure if it matters. For some reason people use this, though. + + +To Deprecate +------------ + +(In that, I won't extract these anywhere; I'm not going to do any big deletes anytime soon, though) + +paste.recursive + Better to do it manually (with webob.Request.get_response) + +paste.wsgiwrappers, paste.request, paste.response, paste.wsgilib, paste.httpheaders, paste.httpexceptions: + All the functionality is already in WebOb. + +paste.urlparser.URLParser: + Really this is tied to paste.webkit more than anything. + +paste.auth.*: + Well, these all need to be refactored, and replacements exist in AuthKit and repoze.who. Some pieces might still have utility. + +paste.debug.profile: + I think repoze.profile supersedes this. + +paste.debug.wdg_validator: + It could get reimplemented with more options for validators, but I'm not really that interested at the moment. The code is nothing fancy. + +paste.transaction: + More general in repoze.tm + +paste.url: + No one uses this + + +Undecided +--------- + +paste.debug.fsdiff: + Maybe ScriptTest? + +paste.session: + It's an okay naive session system. But maybe Beaker makes it irrelevant (Beaker does seem slightly more complex to setup). But then, this can just live here indefinitely. + +paste.gzipper: + I'm a little uncomfortable with this in concept. It's largely in WebOb right now, but not as middleware. + +paste.reloader: + Maybe this should be moved to paste.script (i.e., paster serve) + +paste.debug.debugapp, paste.script.testapp: + Alongside other debugging tools, I guess + +paste.progress: + Not sure this works. diff --git a/docs/include/contact.txt b/docs/include/contact.txt new file mode 100644 index 0000000..87e0bc1 --- /dev/null +++ b/docs/include/contact.txt @@ -0,0 +1,5 @@ +If you have questions about this document, please contact the `paste +mailing list `_ +or try IRC (``#pythonpaste`` on freenode.net). If there's something that +confused you and you want to give feedback, please `submit an issue +`_. diff --git a/docs/include/reference_header.txt b/docs/include/reference_header.txt new file mode 100644 index 0000000..9b73f85 --- /dev/null +++ b/docs/include/reference_header.txt @@ -0,0 +1,5 @@ +Paste Reference Document +@@@@@@@@@@@@@@@@@@@@@@@@ + +.. contents:: + diff --git a/docs/index.txt b/docs/index.txt new file mode 100644 index 0000000..546e9fb --- /dev/null +++ b/docs/index.txt @@ -0,0 +1,69 @@ +Python Paste +============ + +Contents: + +.. toctree:: + :maxdepth: 1 + + news + future + testing-applications + url-parsing-with-wsgi + do-it-yourself-framework + paste-httpserver-threadpool + developer-features + DeveloperGuidelines + StyleGuide + paste-httpserver-threadpool + testing-applications + url-parsing-with-wsgi + community/index.txt + community/mailing-list.txt + community/repository.txt + download/index.txt + license + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. comment: + + I want to put these somewhere sometime, but no place for them now... + Python Paste -- 50% tastier than Elmer's! + Paste: making the web sticky. + Fix broken websites by applying Paste liberally. + Paste: paper over your inconsistencies. + Paste: a soft mixture of malleable consistency. + Paste: a tasty mixture to be spread on bread or crackers. + Paste: glue that won't hurt you if you eat it. + Python Paste: the web extruded into the form of a snake. + Paste: the vinegar eel. + Paste: you bring the cut. + Paste: a doughy substance from which to make metaphorical web cupcakes. + LAMP? LAMPP! + Putting the P in Wep 2.0! + Frankenweb crush tiny humans! + DSL? DSF! + Paste: Comfort for the framework-scarred + +Other Components +================ + +* `Paste Deploy <./deploy/>`_ + +* `Paste Script <./script/>`_ + +* `WebOb `_ + +* `WSGI specification (PEP 333) `_ + +License +======= + +Paste is distributed under the `MIT license +`_. diff --git a/docs/license.txt b/docs/license.txt new file mode 100644 index 0000000..c810dec --- /dev/null +++ b/docs/license.txt @@ -0,0 +1,20 @@ +Copyright (c) 2006-2007 Ian Bicking 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. diff --git a/docs/modules/auth.auth_tkt.txt b/docs/modules/auth.auth_tkt.txt new file mode 100644 index 0000000..8622bcf --- /dev/null +++ b/docs/modules/auth.auth_tkt.txt @@ -0,0 +1,14 @@ +:mod:`paste.auth.auth_tkt` -- auth_tkt cookie parsing +===================================================== + +.. automodule:: paste.auth.auth_tkt + +Module Contents +--------------- + +.. autoclass:: AuthTKTMiddleware +.. autofunction:: make_auth_tkt_middleware +.. autoclass:: AuthTicket +.. autoexception:: BadTicket + + diff --git a/docs/modules/auth.basic.txt b/docs/modules/auth.basic.txt new file mode 100644 index 0000000..2f8f21f --- /dev/null +++ b/docs/modules/auth.basic.txt @@ -0,0 +1,11 @@ +:mod:`paste.auth.basic` -- Basic HTTP authentication +==================================================== + +.. automodule:: paste.auth.basic + +Module Contents +--------------- + +.. autoclass:: AuthBasicAuthenticator +.. autoclass:: AuthBasicHandler +.. autofunction:: make_basic diff --git a/docs/modules/auth.cas.txt b/docs/modules/auth.cas.txt new file mode 100644 index 0000000..d32dd7a --- /dev/null +++ b/docs/modules/auth.cas.txt @@ -0,0 +1,11 @@ +:mod:`paste.auth.cas` -- CAS authentication +=========================================== + +.. automodule:: paste.auth.cas + +Module Contents +--------------- + +.. autoclass:: AuthCASHandler + + diff --git a/docs/modules/auth.cookie.txt b/docs/modules/auth.cookie.txt new file mode 100644 index 0000000..000cb52 --- /dev/null +++ b/docs/modules/auth.cookie.txt @@ -0,0 +1,12 @@ +:mod:`paste.auth.cookie` -- Cookie-based authentication +======================================================= + +.. automodule:: paste.auth.cookie + +Module Contents +--------------- + +.. autoclass:: AuthCookieSigner +.. autoclass:: AuthCookieHandler +.. autoclass:: AuthCookieEnviron +.. autofunction:: make_auth_cookie diff --git a/docs/modules/auth.digest.txt b/docs/modules/auth.digest.txt new file mode 100644 index 0000000..d13357e --- /dev/null +++ b/docs/modules/auth.digest.txt @@ -0,0 +1,12 @@ +:mod:`paste.auth.digest` -- HTTP Digest login +============================================= + +.. automodule:: paste.auth.digest + +Module Contents +--------------- + +.. autoclass:: AuthDigestAuthenticator +.. autoclass:: AuthDigestHandler +.. autofunction:: digest_password +.. autofunction:: make_digest diff --git a/docs/modules/auth.form.txt b/docs/modules/auth.form.txt new file mode 100644 index 0000000..c059589 --- /dev/null +++ b/docs/modules/auth.form.txt @@ -0,0 +1,10 @@ +:mod:`paste.auth.form` -- HTML form/cookie authentication +========================================================= + +.. automodule:: paste.auth.form + +Module Contents +--------------- + +.. autoclass:: AuthFormHandler +.. autofunction:: make_form diff --git a/docs/modules/auth.grantip.txt b/docs/modules/auth.grantip.txt new file mode 100644 index 0000000..ead45d7 --- /dev/null +++ b/docs/modules/auth.grantip.txt @@ -0,0 +1,10 @@ +:mod:`paste.auth.grantip` -- Set user and groups based on IP address +==================================================================== + +.. automodule:: paste.auth.grantip + +Module Contents +--------------- + +.. autoclass:: GrantIPMiddleware +.. autofunction:: make_grantip diff --git a/docs/modules/auth.multi.txt b/docs/modules/auth.multi.txt new file mode 100644 index 0000000..5813ee7 --- /dev/null +++ b/docs/modules/auth.multi.txt @@ -0,0 +1,11 @@ +:mod:`paste.auth.multi` -- Authentication via one of multiple methods +===================================================================== + +.. automodule:: paste.auth.multi + +Module Contents +--------------- + +.. autoclass:: MultiHandler + + diff --git a/docs/modules/cascade.txt b/docs/modules/cascade.txt new file mode 100644 index 0000000..b54c735 --- /dev/null +++ b/docs/modules/cascade.txt @@ -0,0 +1,10 @@ +:mod:`paste.cascade` -- send requests to multiple applications until success +============================================================================ + +.. automodule:: paste.cascade + +Module Contents +--------------- + +.. autoclass:: Cascade +.. autofunction:: make_cascade diff --git a/docs/modules/cgiapp.txt b/docs/modules/cgiapp.txt new file mode 100644 index 0000000..039ec6d --- /dev/null +++ b/docs/modules/cgiapp.txt @@ -0,0 +1,11 @@ +:mod:`paste.cgiapp` -- run CGI scripts as WSGI applications +=========================================================== + +.. automodule:: paste.cgiapp + +Module Contents +--------------- + +.. autoclass:: CGIApplication +.. autoexception:: CGIError +.. autofunction:: make_cgi_application diff --git a/docs/modules/cgitb_catcher.txt b/docs/modules/cgitb_catcher.txt new file mode 100644 index 0000000..44f8771 --- /dev/null +++ b/docs/modules/cgitb_catcher.txt @@ -0,0 +1,10 @@ +:mod:`paste.cgitb_catcher` -- catch exceptions using cgitb +========================================================== + +.. automodule:: paste.cgitb_catcher + +Module Contents +--------------- + +.. autoclass:: CgitbMiddleware +.. autofunction:: make_cgitb_middleware diff --git a/docs/modules/debug.debugapp.txt b/docs/modules/debug.debugapp.txt new file mode 100644 index 0000000..1eb0a54 --- /dev/null +++ b/docs/modules/debug.debugapp.txt @@ -0,0 +1,13 @@ +:mod:`paste.debug.debugapp` -- debug app +======================================== + +.. automodule:: paste.debug.debugapp + +Module Contents +--------------- + +.. autoclass:: SimpleApplication +.. autoclass:: SlowConsumer +.. autofunction:: make_test_app +.. autofunction:: make_slow_app + diff --git a/docs/modules/debug.fsdiff.txt b/docs/modules/debug.fsdiff.txt new file mode 100644 index 0000000..0a267e7 --- /dev/null +++ b/docs/modules/debug.fsdiff.txt @@ -0,0 +1,15 @@ +:mod:`paste.debug.fsdiff` -- Show differences between directories +================================================================= + +.. automodule:: paste.debug.fsdiff + +Module Contents +--------------- + +.. autoclass:: Diff +.. autoclass:: Snapshot +.. autoclass:: File +.. autoclass:: Dir +.. autofunction:: report_expected_diffs +.. autofunction:: show_diff + diff --git a/docs/modules/debug.prints.txt b/docs/modules/debug.prints.txt new file mode 100644 index 0000000..1787e71 --- /dev/null +++ b/docs/modules/debug.prints.txt @@ -0,0 +1,10 @@ +:mod:`paste.debug.prints` -- capture print output +================================================= + +.. automodule:: paste.debug.prints + +Module Contents +--------------- + +.. autoclass:: PrintDebugMiddleware + diff --git a/docs/modules/debug.profile.txt b/docs/modules/debug.profile.txt new file mode 100644 index 0000000..ccc0910 --- /dev/null +++ b/docs/modules/debug.profile.txt @@ -0,0 +1,13 @@ +:mod:`paste.debug.profile` -- profile applications and requests +=============================================================== + +.. automodule:: paste.debug.profile + +Module Contents +--------------- + +.. autoclass:: ProfileMiddleware +.. autofunction:: make_profile_middleware +.. autofunction:: profile_decorator + + diff --git a/docs/modules/debug.watchthreads.txt b/docs/modules/debug.watchthreads.txt new file mode 100644 index 0000000..cd0c915 --- /dev/null +++ b/docs/modules/debug.watchthreads.txt @@ -0,0 +1,12 @@ +:mod:`paste.debug.watchthreads` -- watch thread workers in paste.httpserver +=========================================================================== + +.. automodule:: paste.debug.watchthreads + +Module Contents +--------------- + +.. autoclass:: WatchThreads +.. autofunction:: make_watch_threads +.. autofunction:: make_bad_app + diff --git a/docs/modules/debug.wdg_validate.txt b/docs/modules/debug.wdg_validate.txt new file mode 100644 index 0000000..26f7eff --- /dev/null +++ b/docs/modules/debug.wdg_validate.txt @@ -0,0 +1,11 @@ +:mod:`paste.debug.debugapp` -- debug app +======================================== + +.. automodule:: paste.debug.wdg_validate + +Module Contents +--------------- + +.. autoclass:: WDGValidateMiddleware +.. autofunction:: make_wdg_validate_middleware + diff --git a/docs/modules/errordocument.txt b/docs/modules/errordocument.txt new file mode 100644 index 0000000..111ac18 --- /dev/null +++ b/docs/modules/errordocument.txt @@ -0,0 +1,12 @@ +:mod:`paste.errordocument` -- Do internal redirects for error responses +======================================================================= + +.. automodule:: paste.errordocument + +Module Contents +--------------- + +.. autoclass:: StatusBasedForward +.. autofunction:: make_errordocument + + diff --git a/docs/modules/evalexception.txt b/docs/modules/evalexception.txt new file mode 100644 index 0000000..23587fe --- /dev/null +++ b/docs/modules/evalexception.txt @@ -0,0 +1,9 @@ +:mod:`paste.evalexception` -- Interactive debugging for errors +============================================================== + +.. automodule:: paste.evalexception + +Module Contents +--------------- + +.. autoclass:: EvalException diff --git a/docs/modules/exceptions.txt b/docs/modules/exceptions.txt new file mode 100644 index 0000000..dd1a63f --- /dev/null +++ b/docs/modules/exceptions.txt @@ -0,0 +1,48 @@ +:mod:`paste.exceptions` -- Catch, display, and notify for exceptions +==================================================================== + +.. automodule:: paste.exceptions.errormiddleware + +Module Contents +--------------- + +.. autoclass:: ErrorMiddleware +.. autofunction:: handle_exception +.. autofunction:: make_error_middleware + +:mod:`paste.exceptions.collector` -- Collection information from exceptions +=========================================================================== + +.. automodule:: paste.exceptions.collector + +Module Contents +--------------- + +.. autoclass:: ExceptionCollector +.. autofunction:: collect_exception + +:mod:`paste.exceptions.formatter` -- Format exception information +================================================================= + +.. automodule:: paste.exceptions.formatter + +Module Contents +--------------- + +.. autoclass:: TextFormatter +.. autoclass:: HTMLFormatter +.. autofunction:: format_html +.. autofunction:: format_text + +:mod:`paste.exceptions.reporter` -- Report exceptions +===================================================== + +.. automodule:: paste.exceptions.reporter + +Module Contents +--------------- + +.. autoclass:: EmailReporter +.. autoclass:: LogReporter +.. autoclass:: FileReporter +.. autoclass:: WSGIAppReporter diff --git a/docs/modules/fileapp.txt b/docs/modules/fileapp.txt new file mode 100644 index 0000000..dffefd1 --- /dev/null +++ b/docs/modules/fileapp.txt @@ -0,0 +1,15 @@ +:mod:`paste.fileapp` -- Serve files +=================================== + +.. automodule:: paste.fileapp + +Module Contents +--------------- + +.. autoclass:: FileApp +.. autoclass:: DirectoryApp +.. autofunction:: DataApp +.. autofunction:: ArchiveStore + + + diff --git a/docs/modules/fixture.txt b/docs/modules/fixture.txt new file mode 100644 index 0000000..c519ce2 --- /dev/null +++ b/docs/modules/fixture.txt @@ -0,0 +1,40 @@ +:mod:`paste.fixture` -- Test applications +========================================= + +.. contents:: + +.. automodule:: paste.fixture + +Module Contents +--------------- + +.. autoclass:: TestApp + :members: +.. autoclass:: TestRequest + +Forms +----- + +.. autoclass:: Form + :members: +.. autoclass:: Field + :members: +.. autoclass:: Select +.. autoclass:: Radio +.. autoclass:: Checkbox +.. autoclass:: Text +.. autoclass:: Textarea +.. autoclass:: Hidden +.. autoclass:: Submit + +Script Testing +-------------- + +.. autoclass:: TestFileEnvironment + :members: +.. autoclass:: ProcResult + :members: +.. autoclass:: FoundFile +.. autoclass:: FoundDir + + diff --git a/docs/modules/gzipper.txt b/docs/modules/gzipper.txt new file mode 100644 index 0000000..1036422 --- /dev/null +++ b/docs/modules/gzipper.txt @@ -0,0 +1,10 @@ +:mod:`paste.gzipper` -- Gzip-compress responses +=============================================== + +.. automodule:: paste.gzipper + +Module Contents +--------------- + +.. autoclass:: middleware +.. autofunction:: make_gzip_middleware diff --git a/docs/modules/httpexceptions.txt b/docs/modules/httpexceptions.txt new file mode 100644 index 0000000..736576b --- /dev/null +++ b/docs/modules/httpexceptions.txt @@ -0,0 +1,49 @@ +:mod:`paste.httpexceptions` -- Easily product HTTP errors +========================================================= + +.. automodule:: paste.httpexceptions + +Module Contents +--------------- + +.. autoclass:: HTTPExceptionHandler +.. autofunction:: make_middleware + +Exceptions +---------- + +.. autoexception:: HTTPException +.. autoexception:: HTTPError +.. autoexception:: HTTPRedirection +.. autoexception:: HTTPMultipleChoices +.. autoexception:: HTTPMovedPermanently +.. autoexception:: HTTPFound +.. autoexception:: HTTPNotModified +.. autoexception:: HTTPUseProxy +.. autoexception:: HTTPTemporaryRedirect +.. autoexception:: HTTPClientError +.. autoexception:: HTTPBadRequest +.. autoexception:: HTTPUnauthorized +.. autoexception:: HTTPPaymentRequired +.. autoexception:: HTTPForbidden +.. autoexception:: HTTPNotFound +.. autoexception:: HTTPMethodNotAllowed +.. autoexception:: HTTPNotAcceptable +.. autoexception:: HTTPProxyAuthenticationRequired +.. autoexception:: HTTPRequestTimeout +.. autoexception:: HTTPConflict +.. autoexception:: HTTPGone +.. autoexception:: HTTPLengthRequired +.. autoexception:: HTTPPreconditionFailed +.. autoexception:: HTTPRequestEntityTooLarge +.. autoexception:: HTTPRequestURITooLong +.. autoexception:: HTTPUnsupportedMediaType +.. autoexception:: HTTPRequestRangeNotSatisfiable +.. autoexception:: HTTPExpectationFailed +.. autoexception:: HTTPServerError +.. autoexception:: HTTPInternalServerError +.. autoexception:: HTTPNotImplemented +.. autoexception:: HTTPBadGateway +.. autoexception:: HTTPServiceUnavailable +.. autoexception:: HTTPGatewayTimeout +.. autoexception:: HTTPVersionNotSupported diff --git a/docs/modules/httpheaders.txt b/docs/modules/httpheaders.txt new file mode 100644 index 0000000..ef5c74b --- /dev/null +++ b/docs/modules/httpheaders.txt @@ -0,0 +1,8 @@ +:mod:`paste.httpheaders` -- Manipulate HTTP Headers +=================================================== + +.. comment: + I just don't feel like documenting the items... + +.. automodule:: paste.httpheaders + :members: diff --git a/docs/modules/httpserver.txt b/docs/modules/httpserver.txt new file mode 100644 index 0000000..5d260c5 --- /dev/null +++ b/docs/modules/httpserver.txt @@ -0,0 +1,10 @@ +:mod:`paste.httpserver` -- HTTP server +====================================== + +.. automodule:: paste.httpserver + +Module Contents +--------------- + +.. autofunction:: serve +.. autofunction:: server_runner diff --git a/docs/modules/lint.txt b/docs/modules/lint.txt new file mode 100644 index 0000000..7a21caf --- /dev/null +++ b/docs/modules/lint.txt @@ -0,0 +1,10 @@ +:mod:`paste.lint` -- Check for the validity of WSGI requests and responses +========================================================================== + +.. automodule:: paste.lint + +Module Contents +--------------- + +.. autofunction:: middleware +.. autoexception:: WSGIWarning diff --git a/docs/modules/pony.txt b/docs/modules/pony.txt new file mode 100644 index 0000000..b2c281b --- /dev/null +++ b/docs/modules/pony.txt @@ -0,0 +1,10 @@ +:mod:`paste.pony` -- Add pony power to your application +======================================================= + +.. automodule:: paste.pony + +Module Contents +--------------- + +.. autoclass:: PonyMiddleware +.. autofunction:: make_pony diff --git a/docs/modules/progress.txt b/docs/modules/progress.txt new file mode 100644 index 0000000..8b15dc8 --- /dev/null +++ b/docs/modules/progress.txt @@ -0,0 +1,13 @@ +:mod:`paste.progress` -- Track progress of uploads +================================================== + +.. automodule:: paste.progress + +Module Contents +--------------- + +.. autoclass:: UploadProgressMonitor +.. autoclass:: UploadProgressReporter + + + diff --git a/docs/modules/proxy.txt b/docs/modules/proxy.txt new file mode 100644 index 0000000..1e6841d --- /dev/null +++ b/docs/modules/proxy.txt @@ -0,0 +1,14 @@ +:mod:`paste.proxy` -- Proxy WSGI requests to HTTP requests +========================================================== + +.. automodule:: paste.proxy + +Module Contents +--------------- + +.. autoclass:: Proxy +.. autofunction:: make_proxy +.. autoclass:: TransparentProxy +.. autofunction:: make_transparent_proxy + + diff --git a/docs/modules/recursive.txt b/docs/modules/recursive.txt new file mode 100644 index 0000000..a9339de --- /dev/null +++ b/docs/modules/recursive.txt @@ -0,0 +1,10 @@ +:mod:`paste.recursive` -- internal requests +=========================================== + +.. automodule:: paste.recursive + +Module Contents +--------------- + +.. autoclass:: RecursiveMiddleware +.. autofunction:: ForwardRequestException diff --git a/docs/modules/registry.txt b/docs/modules/registry.txt new file mode 100644 index 0000000..aba5bce --- /dev/null +++ b/docs/modules/registry.txt @@ -0,0 +1,13 @@ +:mod:`paste.registry` -- Manage thread-local request-specific objects +===================================================================== + +.. automodule:: paste.registry + +Module Contents +--------------- + +.. autoclass:: StackedObjectProxy +.. autoclass:: Registry +.. autoclass:: RegistryManager +.. autoclass:: StackedObjectRestorer +.. autofunction:: make_registry_manager diff --git a/docs/modules/reloader.txt b/docs/modules/reloader.txt new file mode 100644 index 0000000..fb27333 --- /dev/null +++ b/docs/modules/reloader.txt @@ -0,0 +1,14 @@ +:mod:`paste.reloader` -- Monitor for file changes to restart the process +======================================================================== + +.. automodule:: paste.reloader + +Module Contents +--------------- + +.. autofunction:: install +.. autoclass:: Monitor +.. autofunction:: watch_file + + + diff --git a/docs/modules/request.txt b/docs/modules/request.txt new file mode 100644 index 0000000..d37b129 --- /dev/null +++ b/docs/modules/request.txt @@ -0,0 +1,19 @@ +:mod:`paste.request` -- Utility functions for the WSGI environment +================================================================== + +.. automodule:: paste.request + +Module Contents +--------------- + +.. autofunction:: get_cookies +.. autofunction:: get_cookie_dict +.. autofunction:: parse_querystring +.. autofunction:: parse_formvars +.. autofunction:: construct_url +.. autofunction:: path_info_split +.. autofunction:: path_info_pop +.. autofunction:: resolve_relative_url +.. autoclass:: EnvironHeaders + + diff --git a/docs/modules/response.txt b/docs/modules/response.txt new file mode 100644 index 0000000..1b6c129 --- /dev/null +++ b/docs/modules/response.txt @@ -0,0 +1,15 @@ +:mod:`paste.response` -- Utility functions for producing responses +================================================================== + +.. automodule:: paste.response + +Module Contents +--------------- + +.. autoclass:: HeaderDict +.. autofunction:: has_header +.. autofunction:: header_value +.. autofunction:: remove_header +.. autofunction:: replace_header + + diff --git a/docs/modules/session.txt b/docs/modules/session.txt new file mode 100644 index 0000000..6a11dfd --- /dev/null +++ b/docs/modules/session.txt @@ -0,0 +1,11 @@ +:mod:`paste.session` -- Simple file-based sessions +================================================== + +.. automodule:: paste.session + +Module Contents +--------------- + +.. autoclass:: SessionMiddleware +.. autofunction:: make_session_middleware + diff --git a/docs/modules/transaction.txt b/docs/modules/transaction.txt new file mode 100644 index 0000000..1e23a3e --- /dev/null +++ b/docs/modules/transaction.txt @@ -0,0 +1,11 @@ +:mod:`paste.transaction` -- DB-API transactions +=============================================== + +.. automodule:: paste.transaction + +Module Contents +--------------- + +.. autoclass:: TransactionManagerMiddleware +.. autoclass:: ConnectionFactory +.. autofunction:: BasicTransactionHandler diff --git a/docs/modules/translogger.txt b/docs/modules/translogger.txt new file mode 100644 index 0000000..84a7217 --- /dev/null +++ b/docs/modules/translogger.txt @@ -0,0 +1,10 @@ +:mod:`paste.translogger` -- Log requests +======================================== + +.. automodule:: paste.translogger + +Module Contents +--------------- + +.. autoclass:: TransLogger +.. autofunction:: make_filter diff --git a/docs/modules/url.txt b/docs/modules/url.txt new file mode 100644 index 0000000..6b5e83f --- /dev/null +++ b/docs/modules/url.txt @@ -0,0 +1,10 @@ +:mod:`paste.url` -- URL convenience class +========================================= + +.. automodule:: paste.url + +Module Contents +--------------- + +.. autoclass:: URL +.. autoclass:: Image diff --git a/docs/modules/urlmap.txt b/docs/modules/urlmap.txt new file mode 100644 index 0000000..ae584d9 --- /dev/null +++ b/docs/modules/urlmap.txt @@ -0,0 +1,11 @@ +:mod:`paste.urlmap` -- Map URL paths +==================================== + +.. automodule:: paste.urlmap + +Module Contents +--------------- + +.. autoclass:: URLMap +.. autofunction:: urlmap_factory +.. autoclass:: PathProxyURLMap diff --git a/docs/modules/urlparser.txt b/docs/modules/urlparser.txt new file mode 100644 index 0000000..28752ab --- /dev/null +++ b/docs/modules/urlparser.txt @@ -0,0 +1,14 @@ +:mod:`paste.urlparser` -- Handle URL paths and server static files +================================================================== + +.. automodule:: paste.urlparser + +Module Contents +--------------- + +.. autoclass:: StaticURLParser +.. autofunction:: make_static +.. autoclass:: PkgResourcesParser +.. autofunction:: make_pkg_resources +.. autoclass:: URLParser +.. autofunction:: make_url_parser diff --git a/docs/modules/util.import_string.txt b/docs/modules/util.import_string.txt new file mode 100644 index 0000000..04586d1 --- /dev/null +++ b/docs/modules/util.import_string.txt @@ -0,0 +1,12 @@ +:mod:`paste.util.import_string` -- Import objects from strings +============================================================== + +.. automodule:: paste.util.import_string + +Module Contents +--------------- + +.. autofunction:: eval_import +.. autofunction:: simple_import +.. autofunction:: import_module +.. autofunction:: try_import_module diff --git a/docs/modules/util.multidict.txt b/docs/modules/util.multidict.txt new file mode 100644 index 0000000..58b094a --- /dev/null +++ b/docs/modules/util.multidict.txt @@ -0,0 +1,11 @@ +:mod:`paste.util.multidict` -- Dictionaries with multiple values +================================================================ + +.. automodule:: paste.util.multidict + +Module Contents +--------------- + +.. autoclass:: MultiDict +.. autoclass:: UnicodeMultiDict + diff --git a/docs/modules/wsgilib.txt b/docs/modules/wsgilib.txt new file mode 100644 index 0000000..e40d426 --- /dev/null +++ b/docs/modules/wsgilib.txt @@ -0,0 +1,18 @@ +:mod:`paste.wsgilib` -- Miscellaneous WSGI utility functions +============================================================ + +.. automodule:: paste.wsgilib + +Module Contents +--------------- + +.. autofunction:: add_close +.. autofunction:: add_start_close +.. autofunction:: chained_app_iters +.. autoclass:: encode_unicode_app_iter +.. autofunction:: catch_errors +.. autofunction:: catch_errors_app +.. autofunction:: raw_interactive +.. autofunction:: interactive +.. autofunction:: dump_environ +.. autofunction:: intercept_output diff --git a/docs/modules/wsgiwrappers.txt b/docs/modules/wsgiwrappers.txt new file mode 100644 index 0000000..7774854 --- /dev/null +++ b/docs/modules/wsgiwrappers.txt @@ -0,0 +1,10 @@ +:mod:`paste.wsgiwrappers` -- Wrapper functions for WSGI request and response +============================================================================ + +.. automodule:: paste.wsgiwrappers + +Module Contents +--------------- + +.. autoclass:: WSGIRequest +.. autoclass:: WSGIResponse diff --git a/docs/news.txt b/docs/news.txt new file mode 100644 index 0000000..83ff121 --- /dev/null +++ b/docs/news.txt @@ -0,0 +1,1047 @@ +News +==== + +.. contents:: + +2.0.2 +----- + +* #22: Fix improper commas in request headers in wsgi_environ (https://bitbucket.org/ianb/paste/pull-request/22/fix-improper-commas-in-request-headers-in) + Fixes issue #4 ("WSGI environ totally borked") (https://bitbucket.org/ianb/paste/issue/4/wsgi-environ-totally-borked) + +* #24: test_wsgirequest_charset: Use UTF-8 instead of iso-8859-1 (https://bitbucket.org/ianb/paste/pull-request/24/test_wsgirequest_charset-use-utf-8-instead) + Fixes issue #7 ("Python 3 test failure") (https://bitbucket.org/ianb/paste/issue/7/python-3-test-failure) + +* #23: Replace cgi.parse_qsl w/ six.moves.urllib.parse.parse_qsl (https://bitbucket.org/ianb/paste/pull-request/23/replace-cgiparse_qsl-w) + Fixes issue #8 ("cgi.parse_qsl is pending deprecation") (https://bitbucket.org/ianb/paste/issue/8/cgiparse_qsl-is-pending-deprecation) + +* #20: Escape CGI environment variables in HTTP 404 responses (https://bitbucket.org/ianb/paste/pull-request/20/escape-cgi-environment-variables-in-http) + +* #6: Add HTTP exception for new code 429 "Too Many Requests" (https://bitbucket.org/ianb/paste/pull-request/6/add-http-exception-for-new-code-429-too) + +* #25: replace ``has_key`` method to ``in`` operator #9 (https://bitbucket.org/ianb/paste/pull-request/25/replace-has_key-method-to-in-operator-9) + Fixes #9 ("used methods removed from py3") (https://bitbucket.org/ianb/paste/issue/9/used-methods-removed-from-py3) + +* #5: Invalid error message when the socket is already in use (https://bitbucket.org/ianb/paste/issue/5/invalid-error-message-when-the-socket-is) + +2.0.1 +----- + +* Fix setup.py for six dependency: move the six dependency from extras_require + to install_requires. + +* Port paste.proxy to Python 3. + +* Fix paste.exceptions.serial_number_generator.hash_identifier() on Python 3. + +* Fix paste.util.threadedprint.uninstall(). Rename duplicated uninstall() + function to uninstall_stdin() and fix typo in variable name (_oldstin => + _oldstdin). + +* Add README.rst file. + +2.0 +--- + +* Experimental Python 3 support. + +* paste now requires the six module. + +* Drop support of Python 2.5 and older. + +* Fixed ``egg:Paste#cgi`` + +* In ``paste.httpserver``: give a 100 Continue response even when the + server has been configured as an HTTP/1.0 server (clients may send + ``Expect: 100-Continue`` before they know the version), and wrap + 100 Continue ``environ['wsgi.input']`` files with LimitedLengthFile + just like normal request bodies are wrapped, keeping WSGI + applications from over-reading from the socket. + +* Fixed parsing of paths beginning with multiple forward slashes. + +* Add tox.ini to run tests with tox on Python 2.6, 2.7 and 3.4. + +1.7.5.1 +------- + +* Fix bug introduced in :mod:`paste.auth.auth_tkt` (with ``url_unquote``) + +1.7.5 +----- + +* Won't install ``tests/`` directory (also caused installation + problems on some Mac systems). + +* Fixed problem with gzip middleware and zero-length responses. + +* Use ``X-Forwarded-For`` header in :mod:`paste.translogger` + +* Fixed problems with mimeparse code + +* Fixed some corner cases with CGI scripts + +* :mod:`paste.auth.auth_tkt` will URL-quote usernames, avoiding some + errors with usernames with ``!`` in them. + +* Improve handling of errors in fetching error pages in + :mod:`paste.errordocument`. + +1.7.4 +----- + +* Fix XSS bug (security issue) with not found handlers for + :class:`paste.urlparser.StaticURLParser` and + :class:`paste.urlmap.URLMap`. If you ask for a path with + ``/--> +''' + + resources = '''\ + + +''' + +def insert_head(body, text): + end_head = re.search(r'', body, re.I) + if end_head: + return body[:end_head.start()] + text + body[end_head.end():] + else: + return text + body + +def insert_body(body, text): + end_body = re.search(r'', body, re.I) + if end_body: + return body[:end_body.start()] + text + body[end_body.end():] + else: + return body + text + +def make_cowbell(global_conf, app): + return MoreCowbell(app) + +if __name__ == '__main__': + from paste.debug.debugapp import SimpleApplication + app = MoreCowbell(SimpleApplication()) + from paste.httpserver import serve + serve(app) diff --git a/paste/cowbell/bell-ascending.png b/paste/cowbell/bell-ascending.png new file mode 100644 index 0000000..42f33db Binary files /dev/null and b/paste/cowbell/bell-ascending.png differ diff --git a/paste/cowbell/bell-descending.png b/paste/cowbell/bell-descending.png new file mode 100644 index 0000000..dac8012 Binary files /dev/null and b/paste/cowbell/bell-descending.png differ diff --git a/paste/debug/__init__.py b/paste/debug/__init__.py new file mode 100644 index 0000000..daef7cc --- /dev/null +++ b/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/paste/debug/debugapp.py b/paste/debug/debugapp.py new file mode 100755 index 0000000..f752c36 --- /dev/null +++ b/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 = b"simple" + 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 = "%d bytes" % size + else: + body = ('\n' + '
\n' + '\n' + '\n' + '
\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/paste/debug/doctest_webapp.py b/paste/debug/doctest_webapp.py new file mode 100755 index 0000000..ffcfaa7 --- /dev/null +++ b/paste/debug/doctest_webapp.py @@ -0,0 +1,432 @@ +# (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. +""" + +import 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('%s
\n' + % (url, url)) + out.write('
\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('%s: %s
\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('
%s
' + % errors) + out.write('
\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 = ('
%s
' + % PySourceColor.str2html(data, PySourceColor.dark)) + else: + html = '
%s
' % cgi.escape(data, 1) + html = '%s
%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 + (?:^(?P [ ]*) >>> .*) # PS1 line + (?:\n [ ]* \.\.\. .*)*) # PS2 lines + \n? + # Want consists of any non-blank lines that do not start with PS1. + (?P (?:(?![ ]*$) # 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. + + (?:^\.\.[ ]*(?Prun):[ ]*\n) # Leading command/command + (?:[ ]*\n)? # Blank line following + (?P + (?:(?P [ ]+)[^ ].*$) + (?:\n [ ]+ .*)*) + ) + | + (?: # This is for shell commands + + (?P + (?:^(P [ ]*) [$] .*) # Shell line + (?:\n [ ]* [>] .*)*) # Continuation + \n? + # Want consists of any non-blank lines that do not start with $ + (?P (?:(?![ ]*$) + (?![ ]*[$]$) + .*$\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), '', 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=''): + """ + 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/paste/debug/fsdiff.py b/paste/debug/fsdiff.py new file mode 100644 index 0000000..6f9ec2d --- /dev/null +++ b/paste/debug/fsdiff.py @@ -0,0 +1,408 @@ +# (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 + +try: + # Python 3 + import collections.UserDict as IterableUserDict +except ImportError: + # Python 2.5-2.7 + from UserDict 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 _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/paste/debug/prints.py b/paste/debug/prints.py new file mode 100644 index 0000000..b660bfa --- /dev/null +++ b/paste/debug/prints.py @@ -0,0 +1,149 @@ +# (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:: + +

+
+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 six
+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 = (
+        '
'
+        'Log messages
' + '%s
') + + 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, six.string_types): + 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']*>', re.I) + _explicit_re = re.compile(r']*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', '
') + text = text.replace(' ', '  ') + match = self._explicit_re.search(html) + if not match: + text = self.log_template % text + match = self._body_re.search(html) + if not match: + return text + html + else: + return html[:match.end()] + text + html[match.end():] diff --git a/paste/debug/profile.py b/paste/debug/profile.py new file mode 100644 index 0000000..470a54a --- /dev/null +++ b/paste/debug/profile.py @@ -0,0 +1,228 @@ +# (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 six +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 += '
%s\n%s
' % ( + 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 + six.reraise(exc_info[0], exc_info[1], exc_info[2]) + return result + + def format_function(self, func, *args, **kw): + args = map(repr, args) + args.extend( + ['%s=%r' % (k, v) for k, v in kw.items()]) + return '%s(%s)' % (func.__name__, ', '.join(args)) + + +def make_profile_middleware( + app, global_conf, + log_filename='profile.log.tmp', + 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/paste/debug/testserver.py b/paste/debug/testserver.py new file mode 100755 index 0000000..8044c7c --- /dev/null +++ b/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__': + from six.moves.urllib.request import urlopen + 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 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... + urlopen(baseuri) diff --git a/paste/debug/watchthreads.py b/paste/debug/watchthreads.py new file mode 100644 index 0000000..b06ccea --- /dev/null +++ b/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(''' + + + + {{title}} + + +

{{title}}

+ {{if kill_thread_id}} +
+ Thread {{kill_thread_id}} killed +
+ {{endif}} +
Pool size: {{nworkers}} + {{if actual_workers > nworkers}} + + {{actual_workers-nworkers}} extra + {{endif}} + ({{nworkers_used}} used including current request)
+ 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"])}}
+ +{{for thread in threads}} + + + + + + + + + + + + + + + + +
+ Thread + {{if thread.thread_id == this_thread_id}} + (this request) + {{endif}} + {{thread.thread_id}} + {{if allow_kill}} +
+ + +
+ {{endif}} +
+
Time processing request{{thread.time_html|html}}
URI{{if thread.uri == 'unknown'}} + unknown + {{else}}{{thread.uri_short}} + {{endif}} +
+ ▸ Show environ + + + + {{if thread.traceback}} + ▸ Show traceback + + + {{endif}} + +
+ +{{endfor}} + + + +''', 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 as e: + environ_rows.append( + environ_template.substitute( + key=cgi.escape(str(key)), + value='Error in repr(): %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 '%s' % time_string + else: + return '%s' % 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/paste/debug/wdg_validate.py b/paste/debug/wdg_validate.py new file mode 100644 index 0000000..225baf9 --- /dev/null +++ b/paste/debug/wdg_validate.py @@ -0,0 +1,118 @@ +# (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 `_ +""" + +from cStringIO import StringIO +import subprocess +from paste.response import header_value +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'', 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 = 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 html_errors: + page = self.add_error(page, html_errors)[0] + headers.remove( + ('Content-Length', + str(header_value(headers, 'content-length')))) + headers.append(('Content-Length', str(len(page)))) + return [page] + + 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 = ('
%s
' + % cgi.escape(html_errors)) + match = self._end_body_regex.search(html_page) + if match: + return [html_page[:match.start()] + + add_text + + html_page[match.start():]] + 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/paste/errordocument.py b/paste/errordocument.py new file mode 100644 index 0000000..34f2d4a --- /dev/null +++ b/paste/errordocument.py @@ -0,0 +1,389 @@ +# (c) 2005-2006 James Gardner +# 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 +import sys +from six.moves.urllib import parse as urlparse +from paste.recursive import ForwardRequestException, RecursiveMiddleware, RecursionLoop +from paste.util import converters +from paste.response import replace_header +import six + +def forward(app, codes): + """ + 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 code in codes: + 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) + try: + return self.app(environ, keep_status_start_response) + except RecursionLoop as e: + line = 'Recursion error getting error page: %s\n' % e + if six.PY3: + line = line.encode('utf8') + environ['wsgi.errors'].write(line) + keep_status_start_response('500 Server Error', [('Content-type', 'text/plain')], sys.exc_info()) + body = ('Error: %s. (Error page could not be fetched)' + % self.status) + if six.PY3: + body = body.encode('utf8') + return [body] + + +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 %r' % new_url) + if new_url: + url.append([new_url, status, headers]) + # We have to allow the app to write stuff, even though + # we'll ignore it: + return [].append + 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 = """ + + + Error %(code)s + + +

Error %(code)s

+

%(message)s

+
+

+ Additionally an error occurred trying to produce an + error document. A description of the error was logged + to wsgi.errors. +

+ + + """ + + 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.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: + 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/paste/evalexception/__init__.py b/paste/evalexception/__init__.py new file mode 100644 index 0000000..a19cf85 --- /dev/null +++ b/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/paste/evalexception/evalcontext.py b/paste/evalexception/evalcontext.py new file mode 100644 index 0000000..42f2efa --- /dev/null +++ b/paste/evalexception/evalcontext.py @@ -0,0 +1,69 @@ +# (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 six.moves import cStringIO as StringIO +import traceback +import threading +import pdb +import six +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, '', "single", 0, 1) + six.exec_(code, self.globs, self.namespace) + 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/paste/evalexception/media/MochiKit.packed.js b/paste/evalexception/media/MochiKit.packed.js new file mode 100644 index 0000000..15027d9 --- /dev/null +++ b/paste/evalexception/media/MochiKit.packed.js @@ -0,0 +1,7829 @@ +/*** + + MochiKit.MochiKit 1.4.2 : PACKED VERSION + + THIS FILE IS AUTOMATICALLY GENERATED. If creating patches, please + diff against the source tree, not this file. + + See 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={}; +} +if(typeof (MochiKit.__export__)=="undefined"){ +MochiKit.__export__=(MochiKit.__compat__||(typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")); +} +MochiKit.Base.VERSION="1.4.2"; +MochiKit.Base.NAME="MochiKit.Base"; +MochiKit.Base.update=function(_1,_2){ +if(_1===null||_1===undefined){ +_1={}; +} +for(var i=1;i=0;i--){ +_18.unshift(o[i]); +} +}else{ +res.push(o); +} +} +return res; +},extend:function(_1b,obj,_1d){ +if(!_1d){ +_1d=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(!_1b){ +_1b=[]; +} +for(var i=_1d;i>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 al){ +_93=l; +} +} +_91=[]; +for(i=0;i<_93;i++){ +var _95=[]; +for(var j=1;j=0;i--){ +_b2=[_ae[i].apply(this,_b2)]; +} +return _b2[0]; +}; +},bind:function(_b4,_b5){ +if(typeof (_b4)=="string"){ +_b4=_b5[_b4]; +} +var _b6=_b4.im_func; +var _b7=_b4.im_preargs; +var _b8=_b4.im_self; +var m=MochiKit.Base; +if(typeof (_b4)=="function"&&typeof (_b4.apply)=="undefined"){ +_b4=m._wrapDumbFunction(_b4); +} +if(typeof (_b6)!="function"){ +_b6=_b4; +} +if(typeof (_b5)!="undefined"){ +_b8=_b5; +} +if(typeof (_b7)=="undefined"){ +_b7=[]; +}else{ +_b7=_b7.slice(); +} +m.extend(_b7,arguments,2); +var _ba=function(){ +var _bb=arguments; +var me=arguments.callee; +if(me.im_preargs.length>0){ +_bb=m.concat(me.im_preargs,_bb); +} +var _bd=me.im_self; +if(!_bd){ +_bd=this; +} +return me.im_func.apply(_bd,_bb); +}; +_ba.im_self=_b8; +_ba.im_func=_b6; +_ba.im_preargs=_b7; +return _ba; +},bindLate:function(_be,_bf){ +var m=MochiKit.Base; +if(typeof (_be)!="string"){ +return m.bind.apply(this,arguments); +} +var _c1=m.extend([],arguments,2); +var _c2=function(){ +var _c3=arguments; +var me=arguments.callee; +if(me.im_preargs.length>0){ +_c3=m.concat(me.im_preargs,_c3); +} +var _c5=me.im_self; +if(!_c5){ +_c5=this; +} +return _c5[me.im_func].apply(_c5,_c3); +}; +_c2.im_self=_bf; +_c2.im_func=_be; +_c2.im_preargs=_c1; +return _c2; +},bindMethods:function(_c6){ +var _c7=MochiKit.Base.bind; +for(var k in _c6){ +var _c9=_c6[k]; +if(typeof (_c9)=="function"){ +_c6[k]=_c7(_c9,_c6); +} +} +},registerComparator:function(_ca,_cb,_cc,_cd){ +MochiKit.Base.comparatorRegistry.register(_ca,_cb,_cc,_cd); +},_primitives:{"boolean":true,"string":true,"number":true},compare:function(a,b){ +if(a==b){ +return 0; +} +var _d0=(typeof (a)=="undefined"||a===null); +var _d1=(typeof (b)=="undefined"||b===null); +if(_d0&&_d1){ +return 0; +}else{ +if(_d0){ +return -1; +}else{ +if(_d1){ +return 1; +} +} +} +var m=MochiKit.Base; +var _d3=m._primitives; +if(!(typeof (a) in _d3&&typeof (b) in _d3)){ +try{ +return m.comparatorRegistry.match(a,b); +} +catch(e){ +if(e!=m.NotFound){ +throw e; +} +} +} +if(ab){ +return 1; +} +} +var _d4=m.repr; +throw new TypeError(_d4(a)+" and "+_d4(b)+" can not be compared"); +},compareDateLike:function(a,b){ +return MochiKit.Base.compare(a.getTime(),b.getTime()); +},compareArrayLike:function(a,b){ +var _d9=MochiKit.Base.compare; +var _da=a.length; +var _db=0; +if(_da>b.length){ +_db=1; +_da=b.length; +}else{ +if(_da=0;i--){ +sum+=o[i]; +} +}else{ +sum+=o; +} +} +if(_121<=0){ +throw new TypeError("mean() requires at least one argument"); +} +return sum/_121; +},median:function(){ +var data=MochiKit.Base.flattenArguments(arguments); +if(data.length===0){ +throw new TypeError("median() requires at least one argument"); +} +data.sort(compare); +if(data.length%2==0){ +var _125=data.length/2; +return (data[_125]+data[_125-1])/2; +}else{ +return data[(data.length-1)/2]; +} +},findValue:function(lst,_127,_128,end){ +if(typeof (end)=="undefined"||end===null){ +end=lst.length; +} +if(typeof (_128)=="undefined"||_128===null){ +_128=0; +} +var cmp=MochiKit.Base.compare; +for(var i=_128;i0))){ +var kv=MochiKit.DOM.formContents(_135); +_135=kv[0]; +_136=kv[1]; +}else{ +if(arguments.length==1){ +if(typeof (_135.length)=="number"&&_135.length==2){ +return arguments.callee(_135[0],_135[1]); +} +var o=_135; +_135=[]; +_136=[]; +for(var k in o){ +var v=o[k]; +if(typeof (v)=="function"){ +continue; +}else{ +if(MochiKit.Base.isArrayLike(v)){ +for(var i=0;i=stop){ +throw self.StopIteration; +} +_183+=step; +return rval; +}}; +},imap:function(fun,p,q){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +var _18d=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,_18d)); +}}; +},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 _198=m.map(self.iter,arguments); +return {repr:function(){ +return "chain(...)"; +},toString:m.forwardCall("repr"),next:function(){ +while(_198.length>1){ +try{ +var _199=_198[0].next(); +return _199; +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +_198.shift(); +var _199=_198[0].next(); +return _199; +} +} +if(_198.length==1){ +var arg=_198.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(_1a4,sync,_1a6){ +sync.pos[_1a4]=-1; +var m=MochiKit.Base; +var _1a8=m.listMin; +return {repr:function(){ +return "tee("+_1a4+", ...)"; +},toString:m.forwardCall("repr"),next:function(){ +var rval; +var i=sync.pos[_1a4]; +if(i==sync.max){ +rval=_1a6.next(); +sync.deque.push(rval); +sync.max+=1; +sync.pos[_1a4]+=1; +}else{ +rval=sync.deque[i-sync.min]; +sync.pos[_1a4]+=1; +if(i==sync.min&&_1a8(sync.pos)!=sync.min){ +sync.min+=1; +sync.deque.shift(); +} +} +return rval; +}}; +},tee:function(_1ab,n){ +var rval=[]; +var sync={"pos":[],"deque":[],"max":-1,"min":-1}; +if(arguments.length==1||typeof (n)=="undefined"||n===null){ +n=2; +} +var self=MochiKit.Iter; +_1ab=self.iter(_1ab); +var _tee=self._tee; +for(var i=0;i0&&_1bd>=stop)||(step<0&&_1bd<=stop)){ +throw MochiKit.Iter.StopIteration; +} +var rval=_1bd; +_1bd+=step; +return rval; +},repr:function(){ +return "range("+[_1bd,stop,step].join(", ")+")"; +},toString:MochiKit.Base.forwardCall("repr")}; +},sum:function(_1c1,_1c2){ +if(typeof (_1c2)=="undefined"||_1c2===null){ +_1c2=0; +} +var x=_1c2; +var self=MochiKit.Iter; +_1c1=self.iter(_1c1); +try{ +while(true){ +x+=_1c1.next(); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +return x; +},exhaust:function(_1c5){ +var self=MochiKit.Iter; +_1c5=self.iter(_1c5); +try{ +while(true){ +_1c5.next(); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +},forEach:function(_1c7,func,obj){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +if(arguments.length>2){ +func=m.bind(func,obj); +} +if(m.isArrayLike(_1c7)&&!self.isIterable(_1c7)){ +try{ +for(var i=0;i<_1c7.length;i++){ +func(_1c7[i]); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +}else{ +self.exhaust(self.imap(func,_1c7)); +} +},every:function(_1cd,func){ +var self=MochiKit.Iter; +try{ +self.ifilterfalse(func,_1cd).next(); +return false; +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +return true; +} +},sorted:function(_1d0,cmp){ +var rval=MochiKit.Iter.list(_1d0); +if(arguments.length==1){ +cmp=MochiKit.Base.compare; +} +rval.sort(cmp); +return rval; +},reversed:function(_1d3){ +var rval=MochiKit.Iter.list(_1d3); +rval.reverse(); +return rval; +},some:function(_1d5,func){ +var self=MochiKit.Iter; +try{ +self.ifilter(func,_1d5).next(); +return true; +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +return false; +} +},iextend:function(lst,_1d9){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +if(m.isArrayLike(_1d9)&&!self.isIterable(_1d9)){ +for(var i=0;i<_1d9.length;i++){ +lst.push(_1d9[i]); +} +}else{ +_1d9=self.iter(_1d9); +try{ +while(true){ +lst.push(_1d9.next()); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +} +return lst; +},groupby:function(_1dd,_1de){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +if(arguments.length<2){ +_1de=m.operator.identity; +} +_1dd=self.iter(_1dd); +var pk=undefined; +var k=undefined; +var v; +function fetch(){ +v=_1dd.next(); +k=_1de(v); +} +function eat(){ +var ret=v; +v=undefined; +return ret; +} +var _1e5=true; +var _1e6=m.compare; +return {repr:function(){ +return "groupby(...)"; +},next:function(){ +while(_1e6(k,pk)===0){ +fetch(); +if(_1e5){ +_1e5=false; +break; +} +} +pk=k; +return [k,{next:function(){ +if(v==undefined){ +fetch(); +} +if(_1e6(k,pk)!==0){ +throw self.StopIteration; +} +return eat(); +}}]; +}}; +},groupby_as_array:function(_1e7,_1e8){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +if(arguments.length<2){ +_1e8=m.operator.identity; +} +_1e7=self.iter(_1e7); +var _1eb=[]; +var _1ec=true; +var _1ed; +var _1ee=m.compare; +while(true){ +try{ +var _1ef=_1e7.next(); +var key=_1e8(_1ef); +} +catch(e){ +if(e==self.StopIteration){ +break; +} +throw e; +} +if(_1ec||_1ee(key,_1ed)!==0){ +var _1f1=[]; +_1eb.push([key,_1f1]); +} +_1f1.push(_1ef); +_1ec=false; +_1ed=key; +} +return _1eb; +},arrayLikeIter:function(_1f2){ +var i=0; +return {repr:function(){ +return "arrayLikeIter(...)"; +},toString:MochiKit.Base.forwardCall("repr"),next:function(){ +if(i>=_1f2.length){ +throw MochiKit.Iter.StopIteration; +} +return _1f2[i++]; +}}; +},hasIterateNext:function(_1f4){ +return (_1f4&&typeof (_1f4.iterateNext)=="function"); +},iterateNextIter:function(_1f5){ +return {repr:function(){ +return "iterateNextIter(...)"; +},toString:MochiKit.Base.forwardCall("repr"),next:function(){ +var rval=_1f5.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; +if(typeof (StopIteration)!="undefined"){ +this.StopIteration=StopIteration; +}else{ +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.__export__){ +reduce=MochiKit.Iter.reduce; +} +MochiKit.Base._exportSymbols(this,MochiKit.Iter); +MochiKit.Base._deps("Logging",["Base"]); +MochiKit.Logging.NAME="MochiKit.Logging"; +MochiKit.Logging.VERSION="1.4.2"; +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,_1f9,info){ +this.num=num; +this.level=_1f9; +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(_1fc){ +var self=MochiKit.Logging; +if(typeof (_1fc)=="string"){ +_1fc=self.LogLevel[_1fc]; +} +return function(msg){ +var _1ff=msg.level; +if(typeof (_1ff)=="string"){ +_1ff=self.LogLevel[_1ff]; +} +return _1ff>=_1fc; +}; +},isLogMessage:function(){ +var _200=MochiKit.Logging.LogMessage; +for(var i=0;i=MochiKit.Logging.LogLevel.FATAL){ +_20f="FATAL"; +}else{ +if(_20f>=MochiKit.Logging.LogLevel.ERROR){ +_20f="ERROR"; +}else{ +if(_20f>=MochiKit.Logging.LogLevel.WARNING){ +_20f="WARNING"; +}else{ +if(_20f>=MochiKit.Logging.LogLevel.INFO){ +_20f="INFO"; +}else{ +_20f="DEBUG"; +} +} +} +} +} +var msg=new MochiKit.Logging.LogMessage(this.counter,_20f,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(_212){ +var _213=0; +if(!(typeof (_212)=="undefined"||_212===null)){ +_213=Math.max(0,this._messages.length-_212); +} +return this._messages.slice(_213); +},getMessageText:function(_214){ +if(typeof (_214)=="undefined"||_214===null){ +_214=30; +} +var _215=this.getMessages(_214); +if(_215.length){ +var lst=map(function(m){ +return "\n ["+m.num+"] "+m.level+": "+m.info.join(" "); +},_215); +lst.unshift("LAST "+_215.length+" MESSAGES:"); +return lst.join(""); +} +return ""; +},debuggingBookmarklet:function(_218){ +if(typeof (MochiKit.LoggingPane)=="undefined"){ +alert(this.getMessageText()); +}else{ +MochiKit.LoggingPane.createLoggingPane(_218||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 _21a=m.partial; +var _21b=this.Logger; +var _21c=_21b.prototype.baseLog; +m.update(this.Logger.prototype,{debug:_21a(_21c,"DEBUG"),log:_21a(_21c,"INFO"),error:_21a(_21c,"ERROR"),fatal:_21a(_21c,"FATAL"),warning:_21a(_21c,"WARNING")}); +var self=this; +var _21e=function(name){ +return function(){ +self.logger[name].apply(self.logger,arguments); +}; +}; +this.log=_21e("log"); +this.logError=_21e("error"); +this.logDebug=_21e("debug"); +this.logFatal=_21e("fatal"); +this.logWarning=_21e("warning"); +this.logger=new _21b(); +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); +MochiKit.Base._deps("DateTime",["Base"]); +MochiKit.DateTime.NAME="MochiKit.DateTime"; +MochiKit.DateTime.VERSION="1.4.2"; +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; +} +var date=new Date(iso[0],iso[1]-1,iso[2]); +date.setFullYear(iso[0]); +date.setMonth(iso[1]-1); +date.setDate(iso[2]); +return date; +}; +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,_227,day,hour,min,sec,msec; +year=parseInt(res[1],10); +if(typeof (res[2])=="undefined"||res[2]===""){ +return new Date(year); +} +_227=parseInt(res[2],10)-1; +day=parseInt(res[3],10); +if(typeof (res[4])=="undefined"||res[4]===""){ +return new Date(year,_227,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,_227,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,_227,day,hour,min,sec,msec)-ofs); +}; +MochiKit.DateTime.toISOTime=function(date,_22f){ +if(typeof (date)=="undefined"||date===null){ +return null; +} +var hh=date.getHours(); +var mm=date.getMinutes(); +var ss=date.getSeconds(); +var lst=[((_22f&&(hh<10))?"0"+hh:hh),((mm<10)?"0"+mm:mm),((ss<10)?"0"+ss:ss)]; +return lst.join(":"); +}; +MochiKit.DateTime.toISOTimestamp=function(date,_235){ +if(typeof (date)=="undefined"||date===null){ +return null; +} +var sep=_235?"T":" "; +var foot=_235?"Z":""; +if(_235){ +date=new Date(date.getTime()+(date.getTimezoneOffset()*60000)); +} +return MochiKit.DateTime.toISODate(date)+sep+MochiKit.DateTime.toISOTime(date,_235)+foot; +}; +MochiKit.DateTime.toISODate=function(date){ +if(typeof (date)=="undefined"||date===null){ +return null; +} +var _239=MochiKit.DateTime._padTwo; +var _23a=MochiKit.DateTime._padFour; +return [_23a(date.getFullYear()),_239(date.getMonth()+1),_239(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._padFour=function(n){ +switch(n.toString().length){ +case 1: +return "000"+n; +break; +case 2: +return "00"+n; +break; +case 3: +return "0"+n; +break; +case 4: +default: +return n; +} +}; +MochiKit.DateTime.toPaddedAmericanDate=function(d){ +if(typeof (d)=="undefined"||d===null){ +return null; +} +var _240=MochiKit.DateTime._padTwo; +return [_240(d.getMonth()+1),_240(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(_245,_246){ +if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(MochiKit.__export__===false)){ +var all=_246.EXPORT_TAGS[":all"]; +for(var i=0;i_250){ +var i=_258.length-_250; +res=fmt.separator+_258.substring(i,_258.length)+res; +_258=_258.substring(0,i); +} +} +res=_258+res; +if(_24e>0){ +while(frac.length<_251){ +frac=frac+"0"; +} +res=res+fmt.decimal+frac; +} +return _253+res+_254; +}; +}; +MochiKit.Format.numberFormatter=function(_25c,_25d,_25e){ +if(typeof (_25d)=="undefined"){ +_25d=""; +} +var _25f=_25c.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/); +if(!_25f){ +throw TypeError("Invalid pattern"); +} +var _260=_25c.substr(0,_25f.index); +var _261=_25c.substr(_25f.index+_25f[0].length); +if(_260.search(/-/)==-1){ +_260=_260+"-"; +} +var _262=_25f[1]; +var frac=(typeof (_25f[2])=="string"&&_25f[2]!="")?_25f[2]:""; +var _264=(typeof (_25f[3])=="string"&&_25f[3]!=""); +var tmp=_262.split(/,/); +var _266; +if(typeof (_25e)=="undefined"){ +_25e="default"; +} +if(tmp.length==1){ +_266=null; +}else{ +_266=tmp[1].length; +} +var _267=_262.length-_262.replace(/0/g,"").length; +var _268=frac.length-frac.replace(/0/g,"").length; +var _269=frac.length; +var rval=MochiKit.Format._numberFormatter(_25d,_260,_261,_25e,_264,_269,_267,_266,_268); +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(_26e){ +if(typeof (_26e)=="undefined"||_26e===null){ +_26e="default"; +} +if(typeof (_26e)=="string"){ +var rval=MochiKit.Format.LOCALE[_26e]; +if(typeof (rval)=="string"){ +rval=arguments.callee(rval); +MochiKit.Format.LOCALE[_26e]=rval; +} +return rval; +}else{ +return _26e; +} +}; +MochiKit.Format.twoDigitAverage=function(_270,_271){ +if(_271){ +var res=_270/_271; +if(!isNaN(res)){ +return MochiKit.Format.twoDigitFloat(res); +} +} +return "0"; +}; +MochiKit.Format.twoDigitFloat=function(_273){ +var res=roundToFixed(_273,2); +if(res.indexOf(".00")>0){ +return res.substring(0,res.length-3); +}else{ +if(res.charAt(res.length-1)=="0"){ +return res.substring(0,res.length-1); +}else{ +return res; +} +} +}; +MochiKit.Format.lstrip=function(str,_276){ +str=str+""; +if(typeof (str)!="string"){ +return null; +} +if(!_276){ +return str.replace(/^\s+/,""); +}else{ +return str.replace(new RegExp("^["+_276+"]+"),""); +} +}; +MochiKit.Format.rstrip=function(str,_278){ +str=str+""; +if(typeof (str)!="string"){ +return null; +} +if(!_278){ +return str.replace(/\s+$/,""); +}else{ +return str.replace(new RegExp("["+_278+"]+$"),""); +} +}; +MochiKit.Format.strip=function(str,_27a){ +var self=MochiKit.Format; +return self.rstrip(self.lstrip(str,_27a),_27a); +}; +MochiKit.Format.truncToFixed=function(_27c,_27d){ +var res=Math.floor(_27c).toFixed(0); +if(_27c<0){ +res=Math.ceil(_27c).toFixed(0); +if(res.charAt(0)!="-"&&_27d>0){ +res="-"+res; +} +} +if(res.indexOf("e")<0&&_27d>0){ +var tail=_27c.toString(); +if(tail.indexOf("e")>0){ +tail="."; +}else{ +if(tail.indexOf(".")<0){ +tail="."; +}else{ +tail=tail.substring(tail.indexOf(".")); +} +} +if(tail.length-1>_27d){ +tail=tail.substring(0,_27d+1); +} +while(tail.length-1<_27d){ +tail+="0"; +} +res+=tail; +} +return res; +}; +MochiKit.Format.roundToFixed=function(_280,_281){ +var _282=Math.abs(_280)+0.5*Math.pow(10,-_281); +var res=MochiKit.Format.truncToFixed(_282,_281); +if(_280<0){ +res="-"+res; +} +return res; +}; +MochiKit.Format.percentFormat=function(_284){ +return MochiKit.Format.twoDigitFloat(100*_284)+"%"; +}; +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:"%"},pt_BR:{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(_289,_28a){ +if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(MochiKit.__export__===false)){ +var all=_28a.EXPORT_TAGS[":all"]; +for(var i=0;i1){ +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 _299=this.chain; +var _29a=this.fired; +var res=this.results[_29a]; +var self=this; +var cb=null; +while(_299.length>0&&this.paused===0){ +var pair=_299.shift(); +var f=pair[_29a]; +if(f===null){ +continue; +} +try{ +res=f(res); +_29a=((res instanceof Error)?1:0); +if(res instanceof MochiKit.Async.Deferred){ +cb=function(res){ +self._resback(res); +self.paused--; +if((self.paused===0)&&(self.fired>=0)){ +self._fire(); +} +}; +this.paused++; +} +} +catch(err){ +_29a=1; +if(!(err instanceof Error)){ +err=new MochiKit.Async.GenericError(err); +} +res=err; +} +} +this.fired=_29a; +this.results[_29a]=res; +if(cb&&this.paused){ +res.addBoth(cb); +res.chained=true; +} +}}; +MochiKit.Base.update(MochiKit.Async,{evalJSONRequest:function(req){ +return MochiKit.Base.evalJSON(req.responseText); +},succeed:function(_2a2){ +var d=new MochiKit.Async.Deferred(); +d.callback.apply(d,arguments); +return d; +},fail:function(_2a4){ +var d=new MochiKit.Async.Deferred(); +d.errback.apply(d,arguments); +return d; +},getXMLHttpRequest:function(){ +var self=arguments.callee; +if(!self.XMLHttpRequest){ +var _2a7=[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<_2a7.length;i++){ +var func=_2a7[i]; +try{ +self.XMLHttpRequest=func; +return func(); +} +catch(e){ +} +} +} +return self.XMLHttpRequest(); +},_xhr_onreadystatechange:function(d){ +var m=MochiKit.Base; +if(this.readyState==4){ +try{ +this.onreadystatechange=null; +} +catch(e){ +try{ +this.onreadystatechange=m.noop; +} +catch(e){ +} +} +var _2ac=null; +try{ +_2ac=this.status; +if(!_2ac&&m.isNotEmpty(this.responseText)){ +_2ac=304; +} +} +catch(e){ +} +if(_2ac==200||_2ac==201||_2ac==204||_2ac==304||_2ac==1223){ +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.Base.noop; +} +catch(e){ +} +} +req.abort(); +},sendXMLHttpRequest:function(req,_2b0){ +if(typeof (_2b0)=="undefined"||_2b0===null){ +_2b0=""; +} +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(_2b0); +} +catch(e){ +try{ +req.onreadystatechange=null; +} +catch(ignore){ +} +d.errback(e); +} +return d; +},doXHR:function(url,opts){ +var self=MochiKit.Async; +return self.callLater(0,self._doXHR,url,opts); +},_doXHR:function(url,opts){ +var m=MochiKit.Base; +opts=m.update({method:"GET",sendContent:""},opts); +var self=MochiKit.Async; +var req=self.getXMLHttpRequest(); +if(opts.queryString){ +var qs=m.queryString(opts.queryString); +if(qs){ +url+="?"+qs; +} +} +if("username" in opts){ +req.open(opts.method,url,true,opts.username,opts.password); +}else{ +req.open(opts.method,url,true); +} +if(req.overrideMimeType&&opts.mimeType){ +req.overrideMimeType(opts.mimeType); +} +req.setRequestHeader("X-Requested-With","XMLHttpRequest"); +if(opts.headers){ +var _2bd=opts.headers; +if(!m.isArrayLike(_2bd)){ +_2bd=m.items(_2bd); +} +for(var i=0;i<_2bd.length;i++){ +var _2bf=_2bd[i]; +var name=_2bf[0]; +var _2c1=_2bf[1]; +req.setRequestHeader(name,_2c1); +} +} +return self.sendXMLHttpRequest(req,opts.sendContent); +},_buildURL:function(url){ +if(arguments.length>1){ +var m=MochiKit.Base; +var qs=m.queryString.apply(null,m.extend(null,arguments,1)); +if(qs){ +return url+"?"+qs; +} +} +return url; +},doSimpleXMLHttpRequest:function(url){ +var self=MochiKit.Async; +url=self._buildURL.apply(self,arguments); +return self.doXHR(url); +},loadJSONDoc:function(url){ +var self=MochiKit.Async; +url=self._buildURL.apply(self,arguments); +var d=self.doXHR(url,{"mimeType":"text/plain","headers":[["Accept","application/json"]]}); +d=d.addCallback(self.evalJSONRequest); +return d; +},wait:function(_2ca,_2cb){ +var d=new MochiKit.Async.Deferred(); +var m=MochiKit.Base; +if(typeof (_2cb)!="undefined"){ +d.addCallback(function(){ +return _2cb; +}); +} +var _2ce=setTimeout(m.bind("callback",d),Math.floor(_2ca*1000)); +d.canceller=function(){ +try{ +clearTimeout(_2ce); +} +catch(e){ +} +}; +return d; +},callLater:function(_2cf,func){ +var m=MochiKit.Base; +var _2d2=m.partial.apply(m,m.extend(null,arguments,1)); +return MochiKit.Async.wait(_2cf).addCallback(function(res){ +return _2d2(); +}); +}}); +MochiKit.Async.DeferredLock=function(){ +this.waiting=[]; +this.locked=false; +this.id=this._nextId(); +}; +MochiKit.Async.DeferredLock.prototype={__class__:MochiKit.Async.DeferredLock,acquire:function(){ +var 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 _2d5; +if(this.locked){ +_2d5="locked, "+this.waiting.length+" waiting"; +}else{ +_2d5="unlocked"; +} +return "DeferredLock("+this.id+", "+_2d5+")"; +},toString:MochiKit.Base.forwardCall("repr")}; +MochiKit.Async.DeferredList=function(list,_2d7,_2d8,_2d9,_2da){ +MochiKit.Async.Deferred.apply(this,[_2da]); +this.list=list; +var _2db=[]; +this.resultList=_2db; +this.finishedCount=0; +this.fireOnOneCallback=_2d7; +this.fireOnOneErrback=_2d8; +this.consumeErrors=_2d9; +var cb=MochiKit.Base.bind(this._cbDeferred,this); +for(var i=0;i=0){ +var opt=elem.options[elem.selectedIndex]; +var v=opt.value; +if(!v){ +var h=opt.outerHTML; +if(h&&!h.match(/^[^>]+\svalue\s*=/i)){ +v=opt.text; +} +} +_2fa.push(name); +_2fb.push(v); +return null; +} +_2fa.push(name); +_2fb.push(""); +return null; +}else{ +var opts=elem.options; +if(!opts.length){ +_2fa.push(name); +_2fb.push(""); +return null; +} +for(var i=0;i]+\svalue\s*=/i)){ +v=opt.text; +} +} +_2fa.push(name); +_2fb.push(v); +} +return null; +} +} +if(_300==="FORM"||_300==="P"||_300==="SPAN"||_300==="DIV"){ +return elem.childNodes; +} +_2fa.push(name); +_2fb.push(elem.value||""); +return null; +} +return elem.childNodes; +}); +return [_2fa,_2fb]; +},withDocument:function(doc,func){ +var self=MochiKit.DOM; +var _309=self._document; +var rval; +try{ +self._document=doc; +rval=func(); +} +catch(e){ +self._document=_309; +throw e; +} +self._document=_309; +return rval; +},registerDOMConverter:function(name,_30c,wrap,_30e){ +MochiKit.DOM.domConverters.register(name,_30c,wrap,_30e); +},coerceToDOM:function(node,ctx){ +var m=MochiKit.Base; +var im=MochiKit.Iter; +var self=MochiKit.DOM; +if(im){ +var iter=im.iter; +var _315=im.repeat; +} +var map=m.map; +var _317=self.domConverters; +var _318=arguments.callee; +var _319=m.NotFound; +while(true){ +if(typeof (node)=="undefined"||node===null){ +return null; +} +if(typeof (node)=="function"&&typeof (node.length)=="number"&&!(node instanceof Function)){ +node=im?im.list(node):m.extend(null,node); +} +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.__dom__)=="function"){ +node=node.__dom__(ctx); +continue; +} +if(typeof (node.dom)=="function"){ +node=node.dom(ctx); +continue; +} +if(typeof (node)=="function"){ +node=node.apply(ctx,[ctx]); +continue; +} +if(im){ +var _31a=null; +try{ +_31a=iter(node); +} +catch(e){ +} +if(_31a){ +return map(_318,_31a,_315(ctx)); +} +}else{ +if(m.isArrayLike(node)){ +var func=function(n){ +return _318(n,ctx); +}; +return map(func,node); +} +} +try{ +node=_317.match(node,ctx); +continue; +} +catch(e){ +if(e!=_319){ +throw e; +} +} +return self._document.createTextNode(node.toString()); +} +return undefined; +},isChildNode:function(node,_31e){ +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +node=self.getElement(node); +} +if(typeof (_31e)=="string"){ +_31e=self.getElement(_31e); +} +if(typeof (node)=="undefined"||node===null){ +return false; +} +while(node!=null&&node!==self._document){ +if(node===_31e){ +return true; +} +node=node.parentNode; +} +return false; +},setNodeAttribute:function(node,attr,_322){ +var o={}; +o[attr]=_322; +try{ +return MochiKit.DOM.updateNodeAttributes(node,o); +} +catch(e){ +} +return null; +},getNodeAttribute:function(node,attr){ +var self=MochiKit.DOM; +var _327=self.attributeArray.renames[attr]; +var _328=self.attributeArray.ignoreAttr[attr]; +node=self.getElement(node); +try{ +if(_327){ +return node[_327]; +} +var _329=node.getAttribute(attr); +if(_329!=_328){ +return _329; +} +} +catch(e){ +} +return null; +},removeNodeAttribute:function(node,attr){ +var self=MochiKit.DOM; +var _32d=self.attributeArray.renames[attr]; +node=self.getElement(node); +try{ +if(_32d){ +return node[_32d]; +} +return node.removeAttribute(attr); +} +catch(e){ +} +return null; +},updateNodeAttributes:function(node,_32f){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +} +if(_32f){ +var _332=MochiKit.Base.updatetree; +if(self.attributeArray.compliant){ +for(var k in _32f){ +var v=_32f[k]; +if(typeof (v)=="object"&&typeof (elem[k])=="object"){ +if(k=="style"&&MochiKit.Style){ +MochiKit.Style.setStyle(elem,v); +}else{ +_332(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); +} +} +if(typeof (elem[k])=="string"&&elem[k]!=v){ +elem[k]=v; +} +} +}else{ +var _335=self.attributeArray.renames; +for(var k in _32f){ +v=_32f[k]; +var _336=_335[k]; +if(k=="style"&&typeof (v)=="string"){ +elem.style.cssText=v; +}else{ +if(typeof (_336)=="string"){ +elem[_336]=v; +}else{ +if(typeof (elem[k])=="object"&&typeof (v)=="object"){ +if(k=="style"&&MochiKit.Style){ +MochiKit.Style.setStyle(elem,v); +}else{ +_332(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); +} +} +} +} +if(typeof (elem[k])=="string"&&elem[k]!=v){ +elem[k]=v; +} +} +} +} +return elem; +},appendChildNodes:function(node){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +} +var _33a=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)]; +var _33b=MochiKit.Base.concat; +while(_33a.length){ +var n=_33a.shift(); +if(typeof (n)=="undefined"||n===null){ +}else{ +if(typeof (n.nodeType)=="number"){ +elem.appendChild(n); +}else{ +_33a=_33b(n,_33a); +} +} +} +return elem; +},insertSiblingNodesBefore:function(node){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +} +var _340=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)]; +var _341=elem.parentNode; +var _342=MochiKit.Base.concat; +while(_340.length){ +var n=_340.shift(); +if(typeof (n)=="undefined"||n===null){ +}else{ +if(typeof (n.nodeType)=="number"){ +_341.insertBefore(n,elem); +}else{ +_340=_342(n,_340); +} +} +} +return _341; +},insertSiblingNodesAfter:function(node){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +} +var _347=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)]; +if(elem.nextSibling){ +return self.insertSiblingNodesBefore(elem.nextSibling,_347); +}else{ +return self.appendChildNodes(elem.parentNode,_347); +} +},replaceChildNodes:function(node){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +arguments[0]=elem; +} +var _34b; +while((_34b=elem.firstChild)){ +elem.removeChild(_34b); +} +if(arguments.length<2){ +return elem; +}else{ +return self.appendChildNodes.apply(this,arguments); +} +},createDOM:function(name,_34d){ +var elem; +var self=MochiKit.DOM; +var m=MochiKit.Base; +if(typeof (_34d)=="string"||typeof (_34d)=="number"){ +var args=m.extend([name,null],arguments,1); +return arguments.callee.apply(this,args); +} +if(typeof (name)=="string"){ +var _352=self._xhtml; +if(_34d&&!self.attributeArray.compliant){ +var _353=""; +if("name" in _34d){ +_353+=" name=\""+self.escapeHTML(_34d.name)+"\""; +} +if(name=="input"&&"type" in _34d){ +_353+=" type=\""+self.escapeHTML(_34d.type)+"\""; +} +if(_353){ +name="<"+name+_353+">"; +_352=false; +} +} +var d=self._document; +if(_352&&d===document){ +elem=d.createElementNS("http://www.w3.org/1999/xhtml",name); +}else{ +elem=d.createElement(name); +} +}else{ +elem=name; +} +if(_34d){ +self.updateNodeAttributes(elem,_34d); +} +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)); +},removeElement:function(elem){ +var self=MochiKit.DOM; +var e=self.coerceToDOM(self.getElement(elem)); +e.parentNode.removeChild(e); +return e; +},swapDOM:function(dest,src){ +var self=MochiKit.DOM; +dest=self.getElement(dest); +var _35c=dest.parentNode; +if(src){ +src=self.coerceToDOM(self.getElement(src),_35c); +_35c.replaceChild(src,dest); +}else{ +_35c.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); +} +},getElementsByTagAndClassName:function(_35f,_360,_361){ +var self=MochiKit.DOM; +if(typeof (_35f)=="undefined"||_35f===null){ +_35f="*"; +} +if(typeof (_361)=="undefined"||_361===null){ +_361=self._document; +} +_361=self.getElement(_361); +if(_361==null){ +return []; +} +var _363=(_361.getElementsByTagName(_35f)||self._document.all); +if(typeof (_360)=="undefined"||_360===null){ +return MochiKit.Base.extend(null,_363); +} +var _364=[]; +for(var i=0;i<_363.length;i++){ +var _366=_363[i]; +var cls=_366.className; +if(typeof (cls)!="string"){ +cls=_366.getAttribute("class"); +} +if(typeof (cls)=="string"){ +var _368=cls.split(" "); +for(var j=0;j<_368.length;j++){ +if(_368[j]==_360){ +_364.push(_366); +break; +} +} +} +} +return _364; +},_newCallStack:function(path,once){ +var rval=function(){ +var _36d=arguments.callee.callStack; +for(var i=0;i<_36d.length;i++){ +if(_36d[i].apply(this,arguments)===false){ +break; +} +} +if(once){ +try{ +this[path]=null; +} +catch(e){ +} +} +}; +rval.callStack=[]; +return rval; +},addToCallStack:function(_36f,path,func,once){ +var self=MochiKit.DOM; +var _374=_36f[path]; +var _375=_374; +if(!(typeof (_374)=="function"&&typeof (_374.callStack)=="object"&&_374.callStack!==null)){ +_375=self._newCallStack(path,once); +if(typeof (_374)=="function"){ +_375.callStack.push(_374); +} +_36f[path]=_375; +} +_375.callStack.push(func); +},addLoadEvent:function(func){ +var self=MochiKit.DOM; +self.addToCallStack(self._window,"onload",func,true); +},focusOnLoad:function(_378){ +var self=MochiKit.DOM; +self.addLoadEvent(function(){ +_378=self.getElement(_378); +if(_378){ +_378.focus(); +} +}); +},setElementClass:function(_37a,_37b){ +var self=MochiKit.DOM; +var obj=self.getElement(_37a); +if(self.attributeArray.compliant){ +obj.setAttribute("class",_37b); +}else{ +obj.setAttribute("className",_37b); +} +},toggleElementClass:function(_37e){ +var self=MochiKit.DOM; +for(var i=1;i/g,">"); +},toHTML:function(dom){ +return MochiKit.DOM.emitHTML(dom).join(""); +},emitHTML:function(dom,lst){ +if(typeof (lst)=="undefined"||lst===null){ +lst=[]; +} +var _3a1=[dom]; +var self=MochiKit.DOM; +var _3a3=self.escapeHTML; +var _3a4=self.attributeArray; +while(_3a1.length){ +dom=_3a1.pop(); +if(typeof (dom)=="string"){ +lst.push(dom); +}else{ +if(dom.nodeType==1){ +lst.push("<"+dom.tagName.toLowerCase()); +var _3a5=[]; +var _3a6=_3a4(dom); +for(var i=0;i<_3a6.length;i++){ +var a=_3a6[i]; +_3a5.push([" ",a.name,"=\"",_3a3(a.value),"\""]); +} +_3a5.sort(); +for(i=0;i<_3a5.length;i++){ +var _3a9=_3a5[i]; +for(var j=0;j<_3a9.length;j++){ +lst.push(_3a9[j]); +} +} +if(dom.hasChildNodes()){ +lst.push(">"); +_3a1.push(""); +var _3ab=dom.childNodes; +for(i=_3ab.length-1;i>=0;i--){ +_3a1.push(_3ab[i]); +} +}else{ +lst.push("/>"); +} +}else{ +if(dom.nodeType==3){ +lst.push(_3a3(dom.nodeValue)); +} +} +} +} +return lst; +},scrapeText:function(node,_3ad){ +var rval=[]; +(function(node){ +var cn=node.childNodes; +if(cn){ +for(var i=0;i0){ +var _3ca=m.filter; +_3c9=function(node){ +return _3ca(_3c9.ignoreAttrFilter,node.attributes); +}; +_3c9.ignoreAttr={}; +var _3cc=_3c8.attributes; +var _3cd=_3c9.ignoreAttr; +for(var i=0;i<_3cc.length;i++){ +var a=_3cc[i]; +_3cd[a.name]=a.value; +} +_3c9.ignoreAttrFilter=function(a){ +return (_3c9.ignoreAttr[a.name]!=a.value); +}; +_3c9.compliant=false; +_3c9.renames={"class":"className","checked":"defaultChecked","usemap":"useMap","for":"htmlFor","readonly":"readOnly","colspan":"colSpan","bgcolor":"bgColor","cellspacing":"cellSpacing","cellpadding":"cellPadding"}; +}else{ +_3c9=function(node){ +return node.attributes; +}; +_3c9.compliant=true; +_3c9.ignoreAttr={}; +_3c9.renames={}; +} +this.attributeArray=_3c9; +var _3d2=function(_3d3,arr){ +var _3d5=arr[0]; +var _3d6=arr[1]; +var _3d7=_3d6.split(".")[1]; +var str=""; +str+="if (!MochiKit."+_3d7+") { throw new Error(\""; +str+="This function has been deprecated and depends on MochiKit."; +str+=_3d7+".\");}"; +str+="return "+_3d6+".apply(this, arguments);"; +MochiKit[_3d3][_3d5]=new Function(str); +}; +for(var i=0;i0){ +abort(repr(expr)); +} +},buildMatchExpression:function(){ +var repr=MochiKit.Base.repr; +var _3e4=this.params; +var _3e5=[]; +var _3e6,i; +function childElements(_3e8){ +return "MochiKit.Base.filter(function (node) { return node.nodeType == 1; }, "+_3e8+".childNodes)"; +} +if(_3e4.wildcard){ +_3e5.push("true"); +} +if(_3e6=_3e4.id){ +_3e5.push("element.id == "+repr(_3e6)); +} +if(_3e6=_3e4.tagName){ +_3e5.push("element.tagName.toUpperCase() == "+repr(_3e6)); +} +if((_3e6=_3e4.classNames).length>0){ +for(i=0;i<_3e6.length;i++){ +_3e5.push("MochiKit.DOM.hasElementClass(element, "+repr(_3e6[i])+")"); +} +} +if((_3e6=_3e4.pseudoClassNames).length>0){ +for(i=0;i<_3e6.length;i++){ +var _3e9=_3e6[i].match(/^([^(]+)(?:\((.*)\))?$/); +var _3ea=_3e9[1]; +var _3eb=_3e9[2]; +switch(_3ea){ +case "root": +_3e5.push("element.nodeType == 9 || element === element.ownerDocument.documentElement"); +break; +case "nth-child": +case "nth-last-child": +case "nth-of-type": +case "nth-last-of-type": +_3e9=_3eb.match(/^((?:(\d+)n\+)?(\d+)|odd|even)$/); +if(!_3e9){ +throw "Invalid argument to pseudo element nth-child: "+_3eb; +} +var a,b; +if(_3e9[0]=="odd"){ +a=2; +b=1; +}else{ +if(_3e9[0]=="even"){ +a=2; +b=0; +}else{ +a=_3e9[2]&&parseInt(_3e9)||null; +b=parseInt(_3e9[3]); +} +} +_3e5.push("this.nthChild(element,"+a+","+b+","+!!_3ea.match("^nth-last")+","+!!_3ea.match("of-type$")+")"); +break; +case "first-child": +_3e5.push("this.nthChild(element, null, 1)"); +break; +case "last-child": +_3e5.push("this.nthChild(element, null, 1, true)"); +break; +case "first-of-type": +_3e5.push("this.nthChild(element, null, 1, false, true)"); +break; +case "last-of-type": +_3e5.push("this.nthChild(element, null, 1, true, true)"); +break; +case "only-child": +_3e5.push(childElements("element.parentNode")+".length == 1"); +break; +case "only-of-type": +_3e5.push("MochiKit.Base.filter(function (node) { return node.tagName == element.tagName; }, "+childElements("element.parentNode")+").length == 1"); +break; +case "empty": +_3e5.push("element.childNodes.length == 0"); +break; +case "enabled": +_3e5.push("(this.isUIElement(element) && element.disabled === false)"); +break; +case "disabled": +_3e5.push("(this.isUIElement(element) && element.disabled === true)"); +break; +case "checked": +_3e5.push("(this.isUIElement(element) && element.checked === true)"); +break; +case "not": +var _3ee=new MochiKit.Selector.Selector(_3eb); +_3e5.push("!( "+_3ee.buildMatchExpression()+")"); +break; +} +} +} +if(_3e6=_3e4.attributes){ +MochiKit.Base.map(function(_3ef){ +var _3f0="MochiKit.DOM.getNodeAttribute(element, "+repr(_3ef.name)+")"; +var _3f1=function(_3f2){ +return _3f0+".split("+repr(_3f2)+")"; +}; +_3e5.push(_3f0+" != null"); +switch(_3ef.operator){ +case "=": +_3e5.push(_3f0+" == "+repr(_3ef.value)); +break; +case "~=": +_3e5.push("MochiKit.Base.findValue("+_3f1(" ")+", "+repr(_3ef.value)+") > -1"); +break; +case "^=": +_3e5.push(_3f0+".substring(0, "+_3ef.value.length+") == "+repr(_3ef.value)); +break; +case "$=": +_3e5.push(_3f0+".substring("+_3f0+".length - "+_3ef.value.length+") == "+repr(_3ef.value)); +break; +case "*=": +_3e5.push(_3f0+".match("+repr(_3ef.value)+")"); +break; +case "|=": +_3e5.push(_3f1("-")+"[0].toUpperCase() == "+repr(_3ef.value.toUpperCase())); +break; +case "!=": +_3e5.push(_3f0+" != "+repr(_3ef.value)); +break; +case "": +case undefined: +break; +default: +throw "Unknown operator "+_3ef.operator+" in selector"; +} +},_3e6); +} +return _3e5.join(" && "); +},compileMatcher:function(){ +var code="return (!element.tagName) ? false : "+this.buildMatchExpression()+";"; +this.match=new Function("element",code); +},nthChild:function(_3f4,a,b,_3f7,_3f8){ +var _3f9=MochiKit.Base.filter(function(node){ +return node.nodeType==1; +},_3f4.parentNode.childNodes); +if(_3f8){ +_3f9=MochiKit.Base.filter(function(node){ +return node.tagName==_3f4.tagName; +},_3f9); +} +if(_3f7){ +_3f9=MochiKit.Iter.reversed(_3f9); +} +if(a){ +var _3fc=MochiKit.Base.findIdentical(_3f9,_3f4); +return ((_3fc+1-b)/a)%1==0; +}else{ +return b==MochiKit.Base.findIdentical(_3f9,_3f4)+1; +} +},isUIElement:function(_3fd){ +return MochiKit.Base.findValue(["input","button","select","option","textarea","object"],_3fd.tagName.toLowerCase())>-1; +},findElements:function(_3fe,axis){ +var _400; +if(axis==undefined){ +axis=""; +} +function inScope(_401,_402){ +if(axis==""){ +return MochiKit.DOM.isChildNode(_401,_402); +}else{ +if(axis==">"){ +return _401.parentNode===_402; +}else{ +if(axis=="+"){ +return _401===nextSiblingElement(_402); +}else{ +if(axis=="~"){ +var _403=_402; +while(_403=nextSiblingElement(_403)){ +if(_401===_403){ +return true; +} +} +return false; +}else{ +throw "Invalid axis: "+axis; +} +} +} +} +} +if(_400=MochiKit.DOM.getElement(this.params.id)){ +if(this.match(_400)){ +if(!_3fe||inScope(_400,_3fe)){ +return [_400]; +} +} +} +function nextSiblingElement(node){ +node=node.nextSibling; +while(node&&node.nodeType!=1){ +node=node.nextSibling; +} +return node; +} +if(axis==""){ +_3fe=(_3fe||MochiKit.DOM.currentDocument()).getElementsByTagName(this.params.tagName||"*"); +}else{ +if(axis==">"){ +if(!_3fe){ +throw "> combinator not allowed without preceeding expression"; +} +_3fe=MochiKit.Base.filter(function(node){ +return node.nodeType==1; +},_3fe.childNodes); +}else{ +if(axis=="+"){ +if(!_3fe){ +throw "+ combinator not allowed without preceeding expression"; +} +_3fe=nextSiblingElement(_3fe)&&[nextSiblingElement(_3fe)]; +}else{ +if(axis=="~"){ +if(!_3fe){ +throw "~ combinator not allowed without preceeding expression"; +} +var _406=[]; +while(nextSiblingElement(_3fe)){ +_3fe=nextSiblingElement(_3fe); +_406.push(_3fe); +} +_3fe=_406; +} +} +} +} +if(!_3fe){ +return []; +} +var _407=MochiKit.Base.filter(MochiKit.Base.bind(function(_408){ +return this.match(_408); +},this),_3fe); +return _407; +},repr:function(){ +return "Selector("+this.expression+")"; +},toString:MochiKit.Base.forwardCall("repr")}; +MochiKit.Base.update(MochiKit.Selector,{findChildElements:function(_409,_40a){ +var uniq=function(arr){ +var res=[]; +for(var i=0;i+~]$/)){ +_410=match[0]; +return _412; +}else{ +var _414=new MochiKit.Selector.Selector(expr); +var _415=MochiKit.Iter.reduce(function(_416,_417){ +return MochiKit.Base.extend(_416,_414.findElements(_417||_409,_410)); +},_412,[]); +_410=""; +return _415; +} +}; +var _418=_40f.replace(/(^\s+|\s+$)/g,"").split(/\s+/); +return uniq(MochiKit.Iter.reduce(_411,_418,[null])); +},_40a)); +},findDocElements:function(){ +return MochiKit.Selector.findChildElements(MochiKit.DOM.currentDocument(),arguments); +},__new__:function(){ +var m=MochiKit.Base; +this.$$=this.findDocElements; +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}}); +MochiKit.Selector.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.Selector); +MochiKit.Base._deps("Style",["Base","DOM"]); +MochiKit.Style.NAME="MochiKit.Style"; +MochiKit.Style.VERSION="1.4.2"; +MochiKit.Style.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.Style.toString=function(){ +return this.__repr__(); +}; +MochiKit.Style.EXPORT_OK=[]; +MochiKit.Style.EXPORT=["setStyle","setOpacity","getStyle","getElementDimensions","elementDimensions","setElementDimensions","getElementPosition","elementPosition","setElementPosition","makePositioned","undoPositioned","makeClipping","undoClipping","setDisplayForElement","hideElement","showElement","getViewportDimensions","getViewportPosition","Dimensions","Coordinates"]; +MochiKit.Style.Dimensions=function(w,h){ +this.w=w; +this.h=h; +}; +MochiKit.Style.Dimensions.prototype.__repr__=function(){ +var repr=MochiKit.Base.repr; +return "{w: "+repr(this.w)+", h: "+repr(this.h)+"}"; +}; +MochiKit.Style.Dimensions.prototype.toString=function(){ +return this.__repr__(); +}; +MochiKit.Style.Coordinates=function(x,y){ +this.x=x; +this.y=y; +}; +MochiKit.Style.Coordinates.prototype.__repr__=function(){ +var repr=MochiKit.Base.repr; +return "{x: "+repr(this.x)+", y: "+repr(this.y)+"}"; +}; +MochiKit.Style.Coordinates.prototype.toString=function(){ +return this.__repr__(); +}; +MochiKit.Base.update(MochiKit.Style,{getStyle:function(elem,_421){ +var dom=MochiKit.DOM; +var d=dom._document; +elem=dom.getElement(elem); +_421=MochiKit.Base.camelize(_421); +if(!elem||elem==d){ +return undefined; +} +if(_421=="opacity"&&typeof (elem.filters)!="undefined"){ +var _424=(MochiKit.Style.getStyle(elem,"filter")||"").match(/alpha\(opacity=(.*)\)/); +if(_424&&_424[1]){ +return parseFloat(_424[1])/100; +} +return 1; +} +if(_421=="float"||_421=="cssFloat"||_421=="styleFloat"){ +if(elem.style["float"]){ +return elem.style["float"]; +}else{ +if(elem.style.cssFloat){ +return elem.style.cssFloat; +}else{ +if(elem.style.styleFloat){ +return elem.style.styleFloat; +}else{ +return "none"; +} +} +} +} +var _425=elem.style?elem.style[_421]:null; +if(!_425){ +if(d.defaultView&&d.defaultView.getComputedStyle){ +var css=d.defaultView.getComputedStyle(elem,null); +_421=_421.replace(/([A-Z])/g,"-$1").toLowerCase(); +_425=css?css.getPropertyValue(_421):null; +}else{ +if(elem.currentStyle){ +_425=elem.currentStyle[_421]; +if(/^\d/.test(_425)&&!/px$/.test(_425)&&_421!="fontWeight"){ +var left=elem.style.left; +var _428=elem.runtimeStyle.left; +elem.runtimeStyle.left=elem.currentStyle.left; +elem.style.left=_425||0; +_425=elem.style.pixelLeft+"px"; +elem.style.left=left; +elem.runtimeStyle.left=_428; +} +} +} +} +if(_421=="opacity"){ +_425=parseFloat(_425); +} +if(/Opera/.test(navigator.userAgent)&&(MochiKit.Base.findValue(["left","top","right","bottom"],_421)!=-1)){ +if(MochiKit.Style.getStyle(elem,"position")=="static"){ +_425="auto"; +} +} +return _425=="auto"?null:_425; +},setStyle:function(elem,_42a){ +elem=MochiKit.DOM.getElement(elem); +for(var name in _42a){ +switch(name){ +case "opacity": +MochiKit.Style.setOpacity(elem,_42a[name]); +break; +case "float": +case "cssFloat": +case "styleFloat": +if(typeof (elem.style["float"])!="undefined"){ +elem.style["float"]=_42a[name]; +}else{ +if(typeof (elem.style.cssFloat)!="undefined"){ +elem.style.cssFloat=_42a[name]; +}else{ +elem.style.styleFloat=_42a[name]; +} +} +break; +default: +elem.style[MochiKit.Base.camelize(name)]=_42a[name]; +} +} +},setOpacity:function(elem,o){ +elem=MochiKit.DOM.getElement(elem); +var self=MochiKit.Style; +if(o==1){ +var _42f=/Gecko/.test(navigator.userAgent)&&!(/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent)); +elem.style["opacity"]=_42f?0.999999:1; +if(/MSIE/.test(navigator.userAgent)){ +elem.style["filter"]=self.getStyle(elem,"filter").replace(/alpha\([^\)]*\)/gi,""); +} +}else{ +if(o<0.00001){ +o=0; +} +elem.style["opacity"]=o; +if(/MSIE/.test(navigator.userAgent)){ +elem.style["filter"]=self.getStyle(elem,"filter").replace(/alpha\([^\)]*\)/gi,"")+"alpha(opacity="+o*100+")"; +} +} +},getElementPosition:function(elem,_431){ +var self=MochiKit.Style; +var dom=MochiKit.DOM; +elem=dom.getElement(elem); +if(!elem||(!(elem.x&&elem.y)&&(!elem.parentNode===null||self.getStyle(elem,"display")=="none"))){ +return undefined; +} +var c=new self.Coordinates(0,0); +var box=null; +var _436=null; +var d=MochiKit.DOM._document; +var de=d.documentElement; +var b=d.body; +if(!elem.parentNode&&elem.x&&elem.y){ +c.x+=elem.x||0; +c.y+=elem.y||0; +}else{ +if(elem.getBoundingClientRect){ +box=elem.getBoundingClientRect(); +c.x+=box.left+(de.scrollLeft||b.scrollLeft)-(de.clientLeft||0); +c.y+=box.top+(de.scrollTop||b.scrollTop)-(de.clientTop||0); +}else{ +if(elem.offsetParent){ +c.x+=elem.offsetLeft; +c.y+=elem.offsetTop; +_436=elem.offsetParent; +if(_436!=elem){ +while(_436){ +c.x+=parseInt(_436.style.borderLeftWidth)||0; +c.y+=parseInt(_436.style.borderTopWidth)||0; +c.x+=_436.offsetLeft; +c.y+=_436.offsetTop; +_436=_436.offsetParent; +} +} +var ua=navigator.userAgent.toLowerCase(); +if((typeof (opera)!="undefined"&&parseFloat(opera.version())<9)||(ua.indexOf("AppleWebKit")!=-1&&self.getStyle(elem,"position")=="absolute")){ +c.x-=b.offsetLeft; +c.y-=b.offsetTop; +} +if(elem.parentNode){ +_436=elem.parentNode; +}else{ +_436=null; +} +while(_436){ +var _43b=_436.tagName.toUpperCase(); +if(_43b==="BODY"||_43b==="HTML"){ +break; +} +var disp=self.getStyle(_436,"display"); +if(disp.search(/^inline|table-row.*$/i)){ +c.x-=_436.scrollLeft; +c.y-=_436.scrollTop; +} +if(_436.parentNode){ +_436=_436.parentNode; +}else{ +_436=null; +} +} +} +} +} +if(typeof (_431)!="undefined"){ +_431=arguments.callee(_431); +if(_431){ +c.x-=(_431.x||0); +c.y-=(_431.y||0); +} +} +return c; +},setElementPosition:function(elem,_43e,_43f){ +elem=MochiKit.DOM.getElement(elem); +if(typeof (_43f)=="undefined"){ +_43f="px"; +} +var _440={}; +var _441=MochiKit.Base.isUndefinedOrNull; +if(!_441(_43e.x)){ +_440["left"]=_43e.x+_43f; +} +if(!_441(_43e.y)){ +_440["top"]=_43e.y+_43f; +} +MochiKit.DOM.updateNodeAttributes(elem,{"style":_440}); +},makePositioned:function(_442){ +_442=MochiKit.DOM.getElement(_442); +var pos=MochiKit.Style.getStyle(_442,"position"); +if(pos=="static"||!pos){ +_442.style.position="relative"; +if(/Opera/.test(navigator.userAgent)){ +_442.style.top=0; +_442.style.left=0; +} +} +},undoPositioned:function(_444){ +_444=MochiKit.DOM.getElement(_444); +if(_444.style.position=="relative"){ +_444.style.position=_444.style.top=_444.style.left=_444.style.bottom=_444.style.right=""; +} +},makeClipping:function(_445){ +_445=MochiKit.DOM.getElement(_445); +var s=_445.style; +var _447={"overflow":s.overflow,"overflow-x":s.overflowX,"overflow-y":s.overflowY}; +if((MochiKit.Style.getStyle(_445,"overflow")||"visible")!="hidden"){ +_445.style.overflow="hidden"; +_445.style.overflowX="hidden"; +_445.style.overflowY="hidden"; +} +return _447; +},undoClipping:function(_448,_449){ +_448=MochiKit.DOM.getElement(_448); +if(typeof (_449)=="string"){ +_448.style.overflow=_449; +}else{ +if(_449!=null){ +_448.style.overflow=_449["overflow"]; +_448.style.overflowX=_449["overflow-x"]; +_448.style.overflowY=_449["overflow-y"]; +} +} +},getElementDimensions:function(elem,_44b){ +var self=MochiKit.Style; +var dom=MochiKit.DOM; +if(typeof (elem.w)=="number"||typeof (elem.h)=="number"){ +return new self.Dimensions(elem.w||0,elem.h||0); +} +elem=dom.getElement(elem); +if(!elem){ +return undefined; +} +var disp=self.getStyle(elem,"display"); +if(disp=="none"||disp==""||typeof (disp)=="undefined"){ +var s=elem.style; +var _450=s.visibility; +var _451=s.position; +var _452=s.display; +s.visibility="hidden"; +s.position="absolute"; +s.display=self._getDefaultDisplay(elem); +var _453=elem.offsetWidth; +var _454=elem.offsetHeight; +s.display=_452; +s.position=_451; +s.visibility=_450; +}else{ +_453=elem.offsetWidth||0; +_454=elem.offsetHeight||0; +} +if(_44b){ +var _455="colSpan" in elem&&"rowSpan" in elem; +var _456=(_455&&elem.parentNode&&self.getStyle(elem.parentNode,"borderCollapse")=="collapse"); +if(_456){ +if(/MSIE/.test(navigator.userAgent)){ +var _457=elem.previousSibling?0.5:1; +var _458=elem.nextSibling?0.5:1; +}else{ +var _457=0.5; +var _458=0.5; +} +}else{ +var _457=1; +var _458=1; +} +_453-=Math.round((parseFloat(self.getStyle(elem,"paddingLeft"))||0)+(parseFloat(self.getStyle(elem,"paddingRight"))||0)+_457*(parseFloat(self.getStyle(elem,"borderLeftWidth"))||0)+_458*(parseFloat(self.getStyle(elem,"borderRightWidth"))||0)); +if(_455){ +if(/Gecko|Opera/.test(navigator.userAgent)&&!/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent)){ +var _459=0; +}else{ +if(/MSIE/.test(navigator.userAgent)){ +var _459=1; +}else{ +var _459=_456?0.5:1; +} +} +}else{ +var _459=1; +} +_454-=Math.round((parseFloat(self.getStyle(elem,"paddingTop"))||0)+(parseFloat(self.getStyle(elem,"paddingBottom"))||0)+_459*((parseFloat(self.getStyle(elem,"borderTopWidth"))||0)+(parseFloat(self.getStyle(elem,"borderBottomWidth"))||0))); +} +return new self.Dimensions(_453,_454); +},setElementDimensions:function(elem,_45b,_45c){ +elem=MochiKit.DOM.getElement(elem); +if(typeof (_45c)=="undefined"){ +_45c="px"; +} +var _45d={}; +var _45e=MochiKit.Base.isUndefinedOrNull; +if(!_45e(_45b.w)){ +_45d["width"]=_45b.w+_45c; +} +if(!_45e(_45b.h)){ +_45d["height"]=_45b.h+_45c; +} +MochiKit.DOM.updateNodeAttributes(elem,{"style":_45d}); +},_getDefaultDisplay:function(elem){ +var self=MochiKit.Style; +var dom=MochiKit.DOM; +elem=dom.getElement(elem); +if(!elem){ +return undefined; +} +var _462=elem.tagName.toUpperCase(); +return self._defaultDisplay[_462]||"block"; +},setDisplayForElement:function(_463,_464){ +var _465=MochiKit.Base.extend(null,arguments,1); +var _466=MochiKit.DOM.getElement; +for(var i=0;i<_465.length;i++){ +_464=_466(_465[i]); +if(_464){ +_464.style.display=_463; +} +} +},getViewportDimensions:function(){ +var d=new MochiKit.Style.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&&b.parentElement&&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; +},getViewportPosition:function(){ +var c=new MochiKit.Style.Coordinates(0,0); +var d=MochiKit.DOM._document; +var de=d.documentElement; +var db=d.body; +if(de&&(de.scrollTop||de.scrollLeft)){ +c.x=de.scrollLeft; +c.y=de.scrollTop; +}else{ +if(db){ +c.x=db.scrollLeft; +c.y=db.scrollTop; +} +} +return c; +},__new__:function(){ +var m=MochiKit.Base; +var _470=["A","ABBR","ACRONYM","B","BASEFONT","BDO","BIG","BR","CITE","CODE","DFN","EM","FONT","I","IMG","KBD","LABEL","Q","S","SAMP","SMALL","SPAN","STRIKE","STRONG","SUB","SUP","TEXTAREA","TT","U","VAR"]; +this._defaultDisplay={"TABLE":"table","THEAD":"table-header-group","TBODY":"table-row-group","TFOOT":"table-footer-group","COLGROUP":"table-column-group","COL":"table-column","TR":"table-row","TD":"table-cell","TH":"table-cell","CAPTION":"table-caption","LI":"list-item","INPUT":"inline-block","SELECT":"inline-block"}; +if(/MSIE/.test(navigator.userAgent)){ +for(var k in this._defaultDisplay){ +var v=this._defaultDisplay[k]; +if(v.indexOf("table")==0){ +this._defaultDisplay[k]="block"; +} +} +} +for(var i=0;i<_470.length;i++){ +this._defaultDisplay[_470[i]]="inline"; +} +this.elementPosition=this.getElementPosition; +this.elementDimensions=this.getElementDimensions; +this.hideElement=m.partial(this.setDisplayForElement,"none"); +this.showElement=m.partial(this.setDisplayForElement,"block"); +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}}); +MochiKit.Style.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.Style); +MochiKit.Base._deps("LoggingPane",["Base","Logging"]); +MochiKit.LoggingPane.NAME="MochiKit.LoggingPane"; +MochiKit.LoggingPane.VERSION="1.4.2"; +MochiKit.LoggingPane.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.LoggingPane.toString=function(){ +return this.__repr__(); +}; +MochiKit.LoggingPane.createLoggingPane=function(_474){ +var m=MochiKit.LoggingPane; +_474=!(!_474); +if(m._loggingPane&&m._loggingPane.inline!=_474){ +m._loggingPane.closePane(); +m._loggingPane=null; +} +if(!m._loggingPane||m._loggingPane.closed){ +m._loggingPane=new m.LoggingPane(_474,MochiKit.Logging.logger); +} +return m._loggingPane; +}; +MochiKit.LoggingPane.LoggingPane=function(_476,_477){ +if(typeof (_477)=="undefined"||_477===null){ +_477=MochiKit.Logging.logger; +} +this.logger=_477; +var _478=MochiKit.Base.update; +var _479=MochiKit.Base.updatetree; +var bind=MochiKit.Base.bind; +var _47b=MochiKit.Base.clone; +var win=window; +var uid="_MochiKit_LoggingPane"; +if(typeof (MochiKit.DOM)!="undefined"){ +win=MochiKit.DOM.currentWindow(); +} +if(!_476){ +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(""+"[MochiKit.LoggingPane]"+""); +nwin.document.close(); +nwin.document.title+=" "+win.document.title; +win=nwin; +} +var doc=win.document; +this.doc=doc; +var _482=doc.getElementById(uid); +var _483=!!_482; +if(_482&&typeof (_482.loggingPane)!="undefined"){ +_482.loggingPane.logger=this.logger; +_482.loggingPane.buildAndApplyFilter(); +return _482.loggingPane; +} +if(_483){ +var _484; +while((_484=_482.firstChild)){ +_482.removeChild(_484); +} +}else{ +_482=doc.createElement("div"); +_482.id=uid; +} +_482.loggingPane=this; +var _485=doc.createElement("input"); +var _486=doc.createElement("input"); +var _487=doc.createElement("button"); +var _488=doc.createElement("button"); +var _489=doc.createElement("button"); +var _48a=doc.createElement("button"); +var _48b=doc.createElement("div"); +var _48c=doc.createElement("div"); +var _48d=uid+"_Listener"; +this.colorTable=_47b(this.colorTable); +var _48e=[]; +var _48f=null; +var _490=function(msg){ +var _492=msg.level; +if(typeof (_492)=="number"){ +_492=MochiKit.Logging.LogLevel[_492]; +} +return _492; +}; +var _493=function(msg){ +return msg.info.join(" "); +}; +var _495=bind(function(msg){ +var _497=_490(msg); +var text=_493(msg); +var c=this.colorTable[_497]; +var p=doc.createElement("span"); +p.className="MochiKit-LogMessage MochiKit-LogLevel-"+_497; +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(_497+": "+text)); +_48c.appendChild(p); +_48c.appendChild(doc.createElement("br")); +if(_48b.offsetHeight>_48b.scrollHeight){ +_48b.scrollTop=0; +}else{ +_48b.scrollTop=_48b.scrollHeight; +} +},this); +var _49b=function(msg){ +_48e[_48e.length]=msg; +_495(msg); +}; +var _49d=function(){ +var _49e,_49f; +try{ +_49e=new RegExp(_485.value); +_49f=new RegExp(_486.value); +} +catch(e){ +logDebug("Error in filter regex: "+e.message); +return null; +} +return function(msg){ +return (_49e.test(_490(msg))&&_49f.test(_493(msg))); +}; +}; +var _4a1=function(){ +while(_48c.firstChild){ +_48c.removeChild(_48c.firstChild); +} +}; +var _4a2=function(){ +_48e=[]; +_4a1(); +}; +var _4a3=bind(function(){ +if(this.closed){ +return; +} +this.closed=true; +if(MochiKit.LoggingPane._loggingPane==this){ +MochiKit.LoggingPane._loggingPane=null; +} +this.logger.removeListener(_48d); +try{ +try{ +_482.loggingPane=null; +} +catch(e){ +logFatal("Bookmarklet was closed incorrectly."); +} +if(_476){ +_482.parentNode.removeChild(_482); +}else{ +this.win.close(); +} +} +catch(e){ +} +},this); +var _4a4=function(){ +_4a1(); +for(var i=0;i<_48e.length;i++){ +var msg=_48e[i]; +if(_48f===null||_48f(msg)){ +_495(msg); +} +} +}; +this.buildAndApplyFilter=function(){ +_48f=_49d(); +_4a4(); +this.logger.removeListener(_48d); +this.logger.addListener(_48d,_48f,_49b); +}; +var _4a7=bind(function(){ +_48e=this.logger.getMessages(); +_4a4(); +},this); +var _4a8=bind(function(_4a9){ +_4a9=_4a9||window.event; +key=_4a9.which||_4a9.keyCode; +if(key==13){ +this.buildAndApplyFilter(); +} +},this); +var _4aa="display: block; z-index: 1000; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: "+this.logFont; +if(_476){ +_4aa+="; height: 10em; border-top: 2px solid black"; +}else{ +_4aa+="; height: 100%;"; +} +_482.style.cssText=_4aa; +if(!_483){ +doc.body.appendChild(_482); +} +_4aa={"cssText":"width: 33%; display: inline; font: "+this.logFont}; +_479(_485,{"value":"FATAL|ERROR|WARNING|INFO|DEBUG","onkeypress":_4a8,"style":_4aa}); +_482.appendChild(_485); +_479(_486,{"value":".*","onkeypress":_4a8,"style":_4aa}); +_482.appendChild(_486); +_4aa="width: 8%; display:inline; font: "+this.logFont; +_487.appendChild(doc.createTextNode("Filter")); +_487.onclick=bind("buildAndApplyFilter",this); +_487.style.cssText=_4aa; +_482.appendChild(_487); +_488.appendChild(doc.createTextNode("Load")); +_488.onclick=_4a7; +_488.style.cssText=_4aa; +_482.appendChild(_488); +_489.appendChild(doc.createTextNode("Clear")); +_489.onclick=_4a2; +_489.style.cssText=_4aa; +_482.appendChild(_489); +_48a.appendChild(doc.createTextNode("Close")); +_48a.onclick=_4a3; +_48a.style.cssText=_4aa; +_482.appendChild(_48a); +_48b.style.cssText="overflow: auto; width: 100%"; +_48c.style.cssText="width: 100%; height: "+(_476?"8em":"100%"); +_48b.appendChild(_48c); +_482.appendChild(_48b); +this.buildAndApplyFilter(); +_4a7(); +if(_476){ +this.win=undefined; +}else{ +this.win=win; +} +this.inline=_476; +this.closePane=_4a3; +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); +MochiKit.Base._deps("Color",["Base","DOM","Style"]); +MochiKit.Color.NAME="MochiKit.Color"; +MochiKit.Color.VERSION="1.4.2"; +MochiKit.Color.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.Color.toString=function(){ +return this.__repr__(); +}; +MochiKit.Color.Color=function(red,_4ac,blue,_4ae){ +if(typeof (_4ae)=="undefined"||_4ae===null){ +_4ae=1; +} +this.rgb={r:red,g:_4ac,b:blue,a:_4ae}; +}; +MochiKit.Color.Color.prototype={__class__:MochiKit.Color.Color,colorWithAlpha:function(_4af){ +var rgb=this.rgb; +var m=MochiKit.Color; +return m.Color.fromRGB(rgb.r,rgb.g,rgb.b,_4af); +},colorWithHue:function(hue){ +var hsl=this.asHSL(); +hsl.h=hue; +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},colorWithSaturation:function(_4b5){ +var hsl=this.asHSL(); +hsl.s=_4b5; +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},colorWithLightness:function(_4b8){ +var hsl=this.asHSL(); +hsl.l=_4b8; +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},darkerColorWithLevel:function(_4bb){ +var hsl=this.asHSL(); +hsl.l=Math.max(hsl.l-_4bb,0); +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},lighterColorWithLevel:function(_4be){ +var hsl=this.asHSL(); +hsl.l=Math.min(hsl.l+_4be,1); +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},blendedColor:function(_4c1,_4c2){ +if(typeof (_4c2)=="undefined"||_4c2===null){ +_4c2=0.5; +} +var sf=1-_4c2; +var s=this.rgb; +var d=_4c1.rgb; +var df=_4c2; +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(_4c7){ +var a=this.asRGB(); +var b=_4c7.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,_4de,blue,_4e0){ +var _4e1=MochiKit.Color.Color; +if(arguments.length==1){ +var rgb=red; +red=rgb.r; +_4de=rgb.g; +blue=rgb.b; +if(typeof (rgb.a)=="undefined"){ +_4e0=undefined; +}else{ +_4e0=rgb.a; +} +} +return new _4e1(red,_4de,blue,_4e0); +},fromHSL:function(hue,_4e4,_4e5,_4e6){ +var m=MochiKit.Color; +return m.Color.fromRGB(m.hslToRGB.apply(m,arguments)); +},fromHSV:function(hue,_4e9,_4ea,_4eb){ +var m=MochiKit.Color; +return m.Color.fromRGB(m.hsvToRGB.apply(m,arguments)); +},fromName:function(name){ +var _4ee=MochiKit.Color.Color; +if(name.charAt(0)=="\""){ +name=name.substr(1,name.length-2); +} +var _4ef=_4ee._namedColors[name.toLowerCase()]; +if(typeof (_4ef)=="string"){ +return _4ee.fromHexString(_4ef); +}else{ +if(name=="transparent"){ +return _4ee.transparentColor(); +} +} +return null; +},fromString:function(_4f0){ +var self=MochiKit.Color.Color; +var _4f2=_4f0.substr(0,3); +if(_4f2=="rgb"){ +return self.fromRGBString(_4f0); +}else{ +if(_4f2=="hsl"){ +return self.fromHSLString(_4f0); +}else{ +if(_4f0.charAt(0)=="#"){ +return self.fromHexString(_4f0); +} +} +} +return self.fromName(_4f0); +},fromHexString:function(_4f3){ +if(_4f3.charAt(0)=="#"){ +_4f3=_4f3.substring(1); +} +var _4f4=[]; +var i,hex; +if(_4f3.length==3){ +for(i=0;i<3;i++){ +hex=_4f3.substr(i,1); +_4f4.push(parseInt(hex+hex,16)/255); +} +}else{ +for(i=0;i<6;i+=2){ +hex=_4f3.substr(i,2); +_4f4.push(parseInt(hex,16)/255); +} +} +var _4f7=MochiKit.Color.Color; +return _4f7.fromRGB.apply(_4f7,_4f4); +},_fromColorString:function(pre,_4f9,_4fa,_4fb){ +if(_4fb.indexOf(pre)===0){ +_4fb=_4fb.substring(_4fb.indexOf("(",3)+1,_4fb.length-1); +} +var _4fc=_4fb.split(/\s*,\s*/); +var _4fd=[]; +for(var i=0;i<_4fc.length;i++){ +var c=_4fc[i]; +var val; +var _501=c.substring(c.length-3); +if(c.charAt(c.length-1)=="%"){ +val=0.01*parseFloat(c.substring(0,c.length-1)); +}else{ +if(_501=="deg"){ +val=parseFloat(c)/360; +}else{ +if(_501=="rad"){ +val=parseFloat(c)/(Math.PI*2); +}else{ +val=_4fa[i]*parseFloat(c); +} +} +} +_4fd.push(val); +} +return this[_4f9].apply(this,_4fd); +},fromComputedStyle:function(elem,_503){ +var d=MochiKit.DOM; +var cls=MochiKit.Color.Color; +for(elem=d.getElement(elem);elem;elem=elem.parentNode){ +var _506=MochiKit.Style.getStyle.apply(d,arguments); +if(!_506){ +continue; +} +var _507=cls.fromString(_506); +if(!_507){ +break; +} +if(_507.asRGB().a>0){ +return _507; +} +} +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,_50d){ +v*=_50d; +if(v<0){ +return 0; +}else{ +if(v>_50d){ +return _50d; +}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,_513,_514,_515){ +if(arguments.length==1){ +var hsv=hue; +hue=hsv.h; +_513=hsv.s; +_514=hsv.v; +_515=hsv.a; +} +var red; +var _518; +var blue; +if(_513===0){ +red=_514; +_518=_514; +blue=_514; +}else{ +var i=Math.floor(hue*6); +var f=(hue*6)-i; +var p=_514*(1-_513); +var q=_514*(1-(_513*f)); +var t=_514*(1-(_513*(1-f))); +switch(i){ +case 1: +red=q; +_518=_514; +blue=p; +break; +case 2: +red=p; +_518=_514; +blue=t; +break; +case 3: +red=p; +_518=q; +blue=_514; +break; +case 4: +red=t; +_518=p; +blue=_514; +break; +case 5: +red=_514; +_518=p; +blue=q; +break; +case 6: +case 0: +red=_514; +_518=t; +blue=p; +break; +} +} +return {r:red,g:_518,b:blue,a:_515}; +},hslToRGB:function(hue,_520,_521,_522){ +if(arguments.length==1){ +var hsl=hue; +hue=hsl.h; +_520=hsl.s; +_521=hsl.l; +_522=hsl.a; +} +var red; +var _525; +var blue; +if(_520===0){ +red=_521; +_525=_521; +blue=_521; +}else{ +var m2; +if(_521<=0.5){ +m2=_521*(1+_520); +}else{ +m2=_521+_520-(_521*_520); +} +var m1=(2*_521)-m2; +var f=MochiKit.Color._hslValue; +var h6=hue*6; +red=f(m1,m2,h6+2); +_525=f(m1,m2,h6); +blue=f(m1,m2,h6-2); +} +return {r:red,g:_525,b:blue,a:_522}; +},rgbToHSV:function(red,_52c,blue,_52e){ +if(arguments.length==1){ +var rgb=red; +red=rgb.r; +_52c=rgb.g; +blue=rgb.b; +_52e=rgb.a; +} +var max=Math.max(Math.max(red,_52c),blue); +var min=Math.min(Math.min(red,_52c),blue); +var hue; +var _533; +var _534=max; +if(min==max){ +hue=0; +_533=0; +}else{ +var _535=(max-min); +_533=_535/max; +if(red==max){ +hue=(_52c-blue)/_535; +}else{ +if(_52c==max){ +hue=2+((blue-red)/_535); +}else{ +hue=4+((red-_52c)/_535); +} +} +hue/=6; +if(hue<0){ +hue+=1; +} +if(hue>1){ +hue-=1; +} +} +return {h:hue,s:_533,v:_534,a:_52e}; +},rgbToHSL:function(red,_537,blue,_539){ +if(arguments.length==1){ +var rgb=red; +red=rgb.r; +_537=rgb.g; +blue=rgb.b; +_539=rgb.a; +} +var max=Math.max(red,Math.max(_537,blue)); +var min=Math.min(red,Math.min(_537,blue)); +var hue; +var _53e; +var _53f=(max+min)/2; +var _540=max-min; +if(_540===0){ +hue=0; +_53e=0; +}else{ +if(_53f<=0.5){ +_53e=_540/(max+min); +}else{ +_53e=_540/(2-max-min); +} +if(red==max){ +hue=(_537-blue)/_540; +}else{ +if(_537==max){ +hue=2+((blue-red)/_540); +}else{ +hue=4+((red-_537)/_540); +} +} +hue/=6; +if(hue<0){ +hue+=1; +} +if(hue>1){ +hue-=1; +} +} +return {h:hue,s:_53e,l:_53f,a:_539}; +},toColorPart:function(num){ +num=Math.round(num); +var _542=num.toString(16); +if(num<16){ +return "0"+_542; +} +return _542; +},__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 _544=1/3; +var _545={black:[0,0,0],blue:[0,0,1],brown:[0.6,0.4,0.2],cyan:[0,1,1],darkGray:[_544,_544,_544],gray:[0.5,0.5,0.5],green:[0,1,0],lightGray:[2*_544,2*_544,2*_544],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 _546=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 _545){ +var name=k+"Color"; +var _54f=m.concat([_546,this.Color,name],_545[k]); +this.Color[name]=m.bind.apply(null,_54f); +} +var _550=function(){ +for(var i=0;i1){ +var src=MochiKit.DOM.getElement(arguments[0]); +var sig=arguments[1]; +var obj=arguments[2]; +var func=arguments[3]; +for(var i=_592.length-1;i>=0;i--){ +var o=_592[i]; +if(o.source===src&&o.signal===sig&&o.objOrFunc===obj&&o.funcOrStr===func){ +self._disconnect(o); +if(!self._lock){ +_592.splice(i,1); +}else{ +self._dirty=true; +} +return true; +} +} +}else{ +var idx=m.findIdentical(_592,_590); +if(idx>=0){ +self._disconnect(_590); +if(!self._lock){ +_592.splice(idx,1); +}else{ +self._dirty=true; +} +return true; +} +} +return false; +},disconnectAllTo:function(_59b,_59c){ +var self=MochiKit.Signal; +var _59e=self._observers; +var _59f=self._disconnect; +var _5a0=self._lock; +var _5a1=self._dirty; +if(typeof (_59c)==="undefined"){ +_59c=null; +} +for(var i=_59e.length-1;i>=0;i--){ +var _5a3=_59e[i]; +if(_5a3.objOrFunc===_59b&&(_59c===null||_5a3.funcOrStr===_59c)){ +_59f(_5a3); +if(_5a0){ +_5a1=true; +}else{ +_59e.splice(i,1); +} +} +} +self._dirty=_5a1; +},disconnectAll:function(src,sig){ +src=MochiKit.DOM.getElement(src); +var m=MochiKit.Base; +var _5a7=m.flattenArguments(m.extend(null,arguments,1)); +var self=MochiKit.Signal; +var _5a9=self._disconnect; +var _5aa=self._observers; +var i,_5ac; +var _5ad=self._lock; +var _5ae=self._dirty; +if(_5a7.length===0){ +for(i=_5aa.length-1;i>=0;i--){ +_5ac=_5aa[i]; +if(_5ac.source===src){ +_5a9(_5ac); +if(!_5ad){ +_5aa.splice(i,1); +}else{ +_5ae=true; +} +} +} +}else{ +var sigs={}; +for(i=0;i<_5a7.length;i++){ +sigs[_5a7[i]]=true; +} +for(i=_5aa.length-1;i>=0;i--){ +_5ac=_5aa[i]; +if(_5ac.source===src&&_5ac.signal in sigs){ +_5a9(_5ac); +if(!_5ad){ +_5aa.splice(i,1); +}else{ +_5ae=true; +} +} +} +} +self._dirty=_5ae; +},signal:function(src,sig){ +var self=MochiKit.Signal; +var _5b3=self._observers; +src=MochiKit.DOM.getElement(src); +var args=MochiKit.Base.extend(null,arguments,2); +var _5b5=[]; +self._lock=true; +for(var i=0;i<_5b3.length;i++){ +var _5b7=_5b3[i]; +if(_5b7.source===src&&_5b7.signal===sig&&_5b7.connected){ +try{ +_5b7.listener.apply(src,args); +} +catch(e){ +_5b5.push(e); +} +} +} +self._lock=false; +if(self._dirty){ +self._dirty=false; +for(var i=_5b3.length-1;i>=0;i--){ +if(!_5b3[i].connected){ +_5b3.splice(i,1); +} +} +} +if(_5b5.length==1){ +throw _5b5[0]; +}else{ +if(_5b5.length>1){ +var e=new Error("Multiple errors thrown in handling 'sig', see errors property"); +e.errors=_5b5; +throw e; +} +} +}}); +MochiKit.Signal.EXPORT_OK=[]; +MochiKit.Signal.EXPORT=["connect","disconnect","signal","disconnectAll","disconnectAllTo"]; +MochiKit.Signal.__new__=function(win){ +var m=MochiKit.Base; +this._document=document; +this._window=win; +this._lock=false; +this._dirty=false; +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.__export__){ +connect=MochiKit.Signal.connect; +disconnect=MochiKit.Signal.disconnect; +disconnectAll=MochiKit.Signal.disconnectAll; +signal=MochiKit.Signal.signal; +} +MochiKit.Base._exportSymbols(this,MochiKit.Signal); +MochiKit.Base._deps("Position",["Base","DOM","Style"]); +MochiKit.Position.NAME="MochiKit.Position"; +MochiKit.Position.VERSION="1.4.2"; +MochiKit.Position.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.Position.toString=function(){ +return this.__repr__(); +}; +MochiKit.Position.EXPORT_OK=[]; +MochiKit.Position.EXPORT=[]; +MochiKit.Base.update(MochiKit.Position,{includeScrollOffsets:false,prepare:function(){ +var _5bb=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0; +var _5bc=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0; +this.windowOffset=new MochiKit.Style.Coordinates(_5bb,_5bc); +},cumulativeOffset:function(_5bd){ +var _5be=0; +var _5bf=0; +do{ +_5be+=_5bd.offsetTop||0; +_5bf+=_5bd.offsetLeft||0; +_5bd=_5bd.offsetParent; +}while(_5bd); +return new MochiKit.Style.Coordinates(_5bf,_5be); +},realOffset:function(_5c0){ +var _5c1=0; +var _5c2=0; +do{ +_5c1+=_5c0.scrollTop||0; +_5c2+=_5c0.scrollLeft||0; +_5c0=_5c0.parentNode; +}while(_5c0); +return new MochiKit.Style.Coordinates(_5c2,_5c1); +},within:function(_5c3,x,y){ +if(this.includeScrollOffsets){ +return this.withinIncludingScrolloffsets(_5c3,x,y); +} +this.xcomp=x; +this.ycomp=y; +this.offset=this.cumulativeOffset(_5c3); +if(_5c3.style.position=="fixed"){ +this.offset.x+=this.windowOffset.x; +this.offset.y+=this.windowOffset.y; +} +return (y>=this.offset.y&&y=this.offset.x&&x=this.offset.y&&this.ycomp=this.offset.x&&this.xcomp"+el.innerHTML+""; +},_roundTopCorners:function(el,_5f5,_5f6){ +var _5f7=this._createCorner(_5f6); +for(var i=0;i=0;i--){ +_5fc.appendChild(this._createCornerSlice(_5fa,_5fb,i,"bottom")); +} +el.style.paddingBottom=0; +el.appendChild(_5fc); +},_createCorner:function(_5fe){ +var dom=MochiKit.DOM; +return dom.DIV({style:{backgroundColor:_5fe.toString()}}); +},_createCornerSlice:function(_600,_601,n,_603){ +var _604=MochiKit.DOM.SPAN(); +var _605=_604.style; +_605.backgroundColor=_600.toString(); +_605.display="block"; +_605.height="1px"; +_605.overflow="hidden"; +_605.fontSize="1px"; +var _606=this._borderColor(_600,_601); +if(this.options.border&&n===0){ +_605.borderTopStyle="solid"; +_605.borderTopWidth="1px"; +_605.borderLeftWidth="0px"; +_605.borderRightWidth="0px"; +_605.borderBottomWidth="0px"; +_605.height="0px"; +_605.borderColor=_606.toString(); +}else{ +if(_606){ +_605.borderColor=_606.toString(); +_605.borderStyle="solid"; +_605.borderWidth="0px 1px"; +} +} +if(!this.options.compact&&(n==(this.options.numSlices-1))){ +_605.height="2px"; +} +this._setMargin(_604,n,_603); +this._setBorder(_604,n,_603); +return _604; +},_setOptions:function(_607){ +this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false,__unstable__wrapElement:false}; +MochiKit.Base.update(this.options,_607); +this.options.numSlices=(this.options.compact?2:4); +},_whichSideTop:function(){ +var _608=this.options.corners; +if(this._hasString(_608,"all","top")){ +return ""; +} +var _609=(_608.indexOf("tl")!=-1); +var _60a=(_608.indexOf("tr")!=-1); +if(_609&&_60a){ +return ""; +} +if(_609){ +return "left"; +} +if(_60a){ +return "right"; +} +return ""; +},_whichSideBottom:function(){ +var _60b=this.options.corners; +if(this._hasString(_60b,"all","bottom")){ +return ""; +} +var _60c=(_60b.indexOf("bl")!=-1); +var _60d=(_60b.indexOf("br")!=-1); +if(_60c&&_60d){ +return ""; +} +if(_60c){ +return "left"; +} +if(_60d){ +return "right"; +} +return ""; +},_borderColor:function(_60e,_60f){ +if(_60e=="transparent"){ +return _60f; +}else{ +if(this.options.border){ +return this.options.border; +}else{ +if(this.options.blend){ +return _60f.blendedColor(_60e); +} +} +} +return ""; +},_setMargin:function(el,n,_612){ +var _613=this._marginSize(n)+"px"; +var _614=(_612=="top"?this._whichSideTop():this._whichSideBottom()); +var _615=el.style; +if(_614=="left"){ +_615.marginLeft=_613; +_615.marginRight="0px"; +}else{ +if(_614=="right"){ +_615.marginRight=_613; +_615.marginLeft="0px"; +}else{ +_615.marginLeft=_613; +_615.marginRight=_613; +} +} +},_setBorder:function(el,n,_618){ +var _619=this._borderSize(n)+"px"; +var _61a=(_618=="top"?this._whichSideTop():this._whichSideBottom()); +var _61b=el.style; +if(_61a=="left"){ +_61b.borderLeftWidth=_619; +_61b.borderRightWidth="0px"; +}else{ +if(_61a=="right"){ +_61b.borderRightWidth=_619; +_61b.borderLeftWidth="0px"; +}else{ +_61b.borderLeftWidth=_619; +_61b.borderRightWidth=_619; +} +} +},_marginSize:function(n){ +if(this.isTransparent){ +return 0; +} +var o=this.options; +if(o.compact&&o.blend){ +var _61e=[1,0]; +return _61e[n]; +}else{ +if(o.compact){ +var _61f=[2,1]; +return _61f[n]; +}else{ +if(o.blend){ +var _620=[3,2,1,0]; +return _620[n]; +}else{ +var _621=[5,3,2,1]; +return _621[n]; +} +} +} +},_borderSize:function(n){ +var o=this.options; +var _624; +if(o.compact&&(o.blend||this.isTransparent)){ +return 1; +}else{ +if(o.compact){ +_624=[1,0]; +}else{ +if(o.blend){ +_624=[2,1,1,1]; +}else{ +if(o.border){ +_624=[0,2,0,0]; +}else{ +if(this.isTransparent){ +_624=[5,3,2,1]; +}else{ +return 0; +} +} +} +} +} +return _624[n]; +},_hasString:function(str){ +for(var i=1;i=(_651||i)){ +_651=i; +} +},this.effects); +_64d=_651||_64d; +break; +case "break": +ma(function(e){ +e.finalize(); +},this.effects); +break; +} +_64c.startOn+=_64d; +_64c.finishOn+=_64d; +if(!_64c.options.queue.limit||this.effects.length<_64c.options.queue.limit){ +this.effects.push(_64c); +} +if(!this.interval){ +this.interval=this.startLoop(MochiKit.Base.bind(this.loop,this),40); +} +},startLoop:function(func,_656){ +return setInterval(func,_656); +},remove:function(_657){ +this.effects=MochiKit.Base.filter(function(e){ +return e!=_657; +},this.effects); +if(!this.effects.length){ +this.stopLoop(this.interval); +this.interval=null; +} +},stopLoop:function(_659){ +clearInterval(_659); +},loop:function(){ +var _65a=new Date().getTime(); +MochiKit.Base.map(function(_65b){ +_65b.loop(_65a); +},this.effects); +}}); +MochiKit.Visual.Queues={instances:{},get:function(_65c){ +if(typeof (_65c)!="string"){ +return _65c; +} +if(!this.instances[_65c]){ +this.instances[_65c]=new MochiKit.Visual.ScopedQueue(); +} +return this.instances[_65c]; +}}; +MochiKit.Visual.Queue=MochiKit.Visual.Queues.get("global"); +MochiKit.Visual.DefaultOptions={transition:MochiKit.Visual.Transitions.sinoidal,duration:1,fps:25,sync:false,from:0,to:1,delay:0,queue:"parallel"}; +MochiKit.Visual.Base=function(){ +}; +MochiKit.Visual.Base.prototype={__class__:MochiKit.Visual.Base,start:function(_65d){ +var v=MochiKit.Visual; +this.options=MochiKit.Base.setdefault(_65d,v.DefaultOptions); +this.currentFrame=0; +this.state="idle"; +this.startOn=this.options.delay*1000; +this.finishOn=this.startOn+(this.options.duration*1000); +this.event("beforeStart"); +if(!this.options.sync){ +v.Queues.get(typeof (this.options.queue)=="string"?"global":this.options.queue.scope).add(this); +} +},loop:function(_65f){ +if(_65f>=this.startOn){ +if(_65f>=this.finishOn){ +return this.finalize(); +} +var pos=(_65f-this.startOn)/(this.finishOn-this.startOn); +var _661=Math.round(pos*this.options.fps*this.options.duration); +if(_661>this.currentFrame){ +this.render(pos); +this.currentFrame=_661; +} +} +},render:function(pos){ +if(this.state=="idle"){ +this.state="running"; +this.event("beforeSetup"); +this.setup(); +this.event("afterSetup"); +} +if(this.state=="running"){ +if(this.options.transition){ +pos=this.options.transition(pos); +} +pos*=(this.options.to-this.options.from); +pos+=this.options.from; +this.event("beforeUpdate"); +this.update(pos); +this.event("afterUpdate"); +} +},cancel:function(){ +if(!this.options.sync){ +MochiKit.Visual.Queues.get(typeof (this.options.queue)=="string"?"global":this.options.queue.scope).remove(this); +} +this.state="finished"; +},finalize:function(){ +this.render(1); +this.cancel(); +this.event("beforeFinish"); +this.finish(); +this.event("afterFinish"); +},setup:function(){ +},finish:function(){ +},update:function(_663){ +},event:function(_664){ +if(this.options[_664+"Internal"]){ +this.options[_664+"Internal"](this); +} +if(this.options[_664]){ +this.options[_664](this); +} +},repr:function(){ +return "["+this.__class__.NAME+", options:"+MochiKit.Base.repr(this.options)+"]"; +}}; +MochiKit.Visual.Parallel=function(_665,_666){ +var cls=arguments.callee; +if(!(this instanceof cls)){ +return new cls(_665,_666); +} +this.__init__(_665,_666); +}; +MochiKit.Visual.Parallel.prototype=new MochiKit.Visual.Base(); +MochiKit.Base.update(MochiKit.Visual.Parallel.prototype,{__class__:MochiKit.Visual.Parallel,__init__:function(_668,_669){ +this.effects=_668||[]; +this.start(_669); +},update:function(_66a){ +MochiKit.Base.map(function(_66b){ +_66b.render(_66a); +},this.effects); +},finish:function(){ +MochiKit.Base.map(function(_66c){ +_66c.finalize(); +},this.effects); +}}); +MochiKit.Visual.Sequence=function(_66d,_66e){ +var cls=arguments.callee; +if(!(this instanceof cls)){ +return new cls(_66d,_66e); +} +this.__init__(_66d,_66e); +}; +MochiKit.Visual.Sequence.prototype=new MochiKit.Visual.Base(); +MochiKit.Base.update(MochiKit.Visual.Sequence.prototype,{__class__:MochiKit.Visual.Sequence,__init__:function(_670,_671){ +var defs={transition:MochiKit.Visual.Transitions.linear,duration:0}; +this.effects=_670||[]; +MochiKit.Base.map(function(_673){ +defs.duration+=_673.options.duration; +},this.effects); +MochiKit.Base.setdefault(_671,defs); +this.start(_671); +},update:function(_674){ +var time=_674*this.options.duration; +for(var i=0;i0){ +this.fontSize=parseFloat(_694); +this.fontSizeType=_695; +} +},this),["em","px","%"]); +this.factor=(this.options.scaleTo-this.options.scaleFrom)/100; +if(/^content/.test(this.options.scaleMode)){ +this.dims=[this.element.scrollHeight,this.element.scrollWidth]; +}else{ +if(this.options.scaleMode=="box"){ +this.dims=[this.element.offsetHeight,this.element.offsetWidth]; +}else{ +this.dims=[this.options.scaleMode.originalHeight,this.options.scaleMode.originalWidth]; +} +} +},update:function(_696){ +var _697=(this.options.scaleFrom/100)+(this.factor*_696); +if(this.options.scaleContent&&this.fontSize){ +MochiKit.Style.setStyle(this.element,{fontSize:this.fontSize*_697+this.fontSizeType}); +} +this.setDimensions(this.dims[0]*_697,this.dims[1]*_697); +},finish:function(){ +if(this.restoreAfterFinish){ +MochiKit.Style.setStyle(this.element,this.originalStyle); +} +},setDimensions:function(_698,_699){ +var d={}; +var r=Math.round; +if(/MSIE/.test(navigator.userAgent)){ +r=Math.ceil; +} +if(this.options.scaleX){ +d.width=r(_699)+"px"; +} +if(this.options.scaleY){ +d.height=r(_698)+"px"; +} +if(this.options.scaleFromCenter){ +var topd=(_698-this.dims[0])/2; +var _69d=(_699-this.dims[1])/2; +if(this.elementPositioning=="absolute"){ +if(this.options.scaleY){ +d.top=this.originalTop-topd+"px"; +} +if(this.options.scaleX){ +d.left=this.originalLeft-_69d+"px"; +} +}else{ +if(this.options.scaleY){ +d.top=-topd+"px"; +} +if(this.options.scaleX){ +d.left=-_69d+"px"; +} +} +} +MochiKit.Style.setStyle(this.element,d); +}}); +MochiKit.Visual.Highlight=function(_69e,_69f){ +var cls=arguments.callee; +if(!(this instanceof cls)){ +return new cls(_69e,_69f); +} +this.__init__(_69e,_69f); +}; +MochiKit.Visual.Highlight.prototype=new MochiKit.Visual.Base(); +MochiKit.Base.update(MochiKit.Visual.Highlight.prototype,{__class__:MochiKit.Visual.Highlight,__init__:function(_6a1,_6a2){ +this.element=MochiKit.DOM.getElement(_6a1); +_6a2=MochiKit.Base.update({startcolor:"#ffff99"},_6a2); +this.start(_6a2); +},setup:function(){ +var b=MochiKit.Base; +var s=MochiKit.Style; +if(s.getStyle(this.element,"display")=="none"){ +this.cancel(); +return; +} +this.oldStyle={backgroundImage:s.getStyle(this.element,"background-image")}; +s.setStyle(this.element,{backgroundImage:"none"}); +if(!this.options.endcolor){ +this.options.endcolor=MochiKit.Color.Color.fromBackground(this.element).toHexString(); +} +if(b.isUndefinedOrNull(this.options.restorecolor)){ +this.options.restorecolor=s.getStyle(this.element,"background-color"); +} +this._base=b.map(b.bind(function(i){ +return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16); +},this),[0,1,2]); +this._delta=b.map(b.bind(function(i){ +return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i]; +},this),[0,1,2]); +},update:function(_6a7){ +var m="#"; +MochiKit.Base.map(MochiKit.Base.bind(function(i){ +m+=MochiKit.Color.toColorPart(Math.round(this._base[i]+this._delta[i]*_6a7)); +},this),[0,1,2]); +MochiKit.Style.setStyle(this.element,{backgroundColor:m}); +},finish:function(){ +MochiKit.Style.setStyle(this.element,MochiKit.Base.update(this.oldStyle,{backgroundColor:this.options.restorecolor})); +}}); +MochiKit.Visual.ScrollTo=function(_6aa,_6ab){ +var cls=arguments.callee; +if(!(this instanceof cls)){ +return new cls(_6aa,_6ab); +} +this.__init__(_6aa,_6ab); +}; +MochiKit.Visual.ScrollTo.prototype=new MochiKit.Visual.Base(); +MochiKit.Base.update(MochiKit.Visual.ScrollTo.prototype,{__class__:MochiKit.Visual.ScrollTo,__init__:function(_6ad,_6ae){ +this.element=MochiKit.DOM.getElement(_6ad); +this.start(_6ae); +},setup:function(){ +var p=MochiKit.Position; +p.prepare(); +var _6b0=p.cumulativeOffset(this.element); +if(this.options.offset){ +_6b0.y+=this.options.offset; +} +var max; +if(window.innerHeight){ +max=window.innerHeight-window.height; +}else{ +if(document.documentElement&&document.documentElement.clientHeight){ +max=document.documentElement.clientHeight-document.body.scrollHeight; +}else{ +if(document.body){ +max=document.body.clientHeight-document.body.scrollHeight; +} +} +} +this.scrollStart=p.windowOffset.y; +this.delta=(_6b0.y>max?max:_6b0.y)-this.scrollStart; +},update:function(_6b2){ +var p=MochiKit.Position; +p.prepare(); +window.scrollTo(p.windowOffset.x,this.scrollStart+(_6b2*this.delta)); +}}); +MochiKit.Visual.CSS_LENGTH=/^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; +MochiKit.Visual.Morph=function(_6b4,_6b5){ +var cls=arguments.callee; +if(!(this instanceof cls)){ +return new cls(_6b4,_6b5); +} +this.__init__(_6b4,_6b5); +}; +MochiKit.Visual.Morph.prototype=new MochiKit.Visual.Base(); +MochiKit.Base.update(MochiKit.Visual.Morph.prototype,{__class__:MochiKit.Visual.Morph,__init__:function(_6b7,_6b8){ +this.element=MochiKit.DOM.getElement(_6b7); +this.start(_6b8); +},setup:function(){ +var b=MochiKit.Base; +var _6ba=this.options.style; +this.styleStart={}; +this.styleEnd={}; +this.units={}; +var _6bb,unit; +for(var s in _6ba){ +_6bb=_6ba[s]; +s=b.camelize(s); +if(MochiKit.Visual.CSS_LENGTH.test(_6bb)){ +var _6be=_6bb.match(/^([\+\-]?[0-9\.]+)(.*)$/); +_6bb=parseFloat(_6be[1]); +unit=(_6be.length==3)?_6be[2]:null; +this.styleEnd[s]=_6bb; +this.units[s]=unit; +_6bb=MochiKit.Style.getStyle(this.element,s); +_6be=_6bb.match(/^([\+\-]?[0-9\.]+)(.*)$/); +_6bb=parseFloat(_6be[1]); +this.styleStart[s]=_6bb; +}else{ +if(/[Cc]olor$/.test(s)){ +var c=MochiKit.Color.Color; +_6bb=c.fromString(_6bb); +if(_6bb){ +this.units[s]="color"; +this.styleEnd[s]=_6bb.toHexString(); +_6bb=MochiKit.Style.getStyle(this.element,s); +this.styleStart[s]=c.fromString(_6bb).toHexString(); +this.styleStart[s]=b.map(b.bind(function(i){ +return parseInt(this.styleStart[s].slice(i*2+1,i*2+3),16); +},this),[0,1,2]); +this.styleEnd[s]=b.map(b.bind(function(i){ +return parseInt(this.styleEnd[s].slice(i*2+1,i*2+3),16); +},this),[0,1,2]); +} +}else{ +this.element.style[s]=_6bb; +} +} +} +},update:function(_6c2){ +var _6c3; +for(var s in this.styleStart){ +if(this.units[s]=="color"){ +var m="#"; +var _6c6=this.styleStart[s]; +var end=this.styleEnd[s]; +MochiKit.Base.map(MochiKit.Base.bind(function(i){ +m+=MochiKit.Color.toColorPart(Math.round(_6c6[i]+(end[i]-_6c6[i])*_6c2)); +},this),[0,1,2]); +this.element.style[s]=m; +}else{ +_6c3=this.styleStart[s]+Math.round((this.styleEnd[s]-this.styleStart[s])*_6c2*1000)/1000+this.units[s]; +this.element.style[s]=_6c3; +} +} +}}); +MochiKit.Visual.fade=function(_6c9,_6ca){ +var s=MochiKit.Style; +var _6cc=s.getStyle(_6c9,"opacity"); +_6ca=MochiKit.Base.update({from:s.getStyle(_6c9,"opacity")||1,to:0,afterFinishInternal:function(_6cd){ +if(_6cd.options.to!==0){ +return; +} +s.hideElement(_6cd.element); +s.setStyle(_6cd.element,{"opacity":_6cc}); +}},_6ca); +return new MochiKit.Visual.Opacity(_6c9,_6ca); +}; +MochiKit.Visual.appear=function(_6ce,_6cf){ +var s=MochiKit.Style; +var v=MochiKit.Visual; +_6cf=MochiKit.Base.update({from:(s.getStyle(_6ce,"display")=="none"?0:s.getStyle(_6ce,"opacity")||0),to:1,afterFinishInternal:function(_6d2){ +v.forceRerendering(_6d2.element); +},beforeSetupInternal:function(_6d3){ +s.setStyle(_6d3.element,{"opacity":_6d3.options.from}); +s.showElement(_6d3.element); +}},_6cf); +return new v.Opacity(_6ce,_6cf); +}; +MochiKit.Visual.puff=function(_6d4,_6d5){ +var s=MochiKit.Style; +var v=MochiKit.Visual; +_6d4=MochiKit.DOM.getElement(_6d4); +var _6d8=MochiKit.Style.getElementDimensions(_6d4,true); +var _6d9={position:s.getStyle(_6d4,"position"),top:_6d4.style.top,left:_6d4.style.left,width:_6d4.style.width,height:_6d4.style.height,opacity:s.getStyle(_6d4,"opacity")}; +_6d5=MochiKit.Base.update({beforeSetupInternal:function(_6da){ +MochiKit.Position.absolutize(_6da.effects[0].element); +},afterFinishInternal:function(_6db){ +s.hideElement(_6db.effects[0].element); +s.setStyle(_6db.effects[0].element,_6d9); +},scaleContent:true,scaleFromCenter:true},_6d5); +return new v.Parallel([new v.Scale(_6d4,200,{sync:true,scaleFromCenter:_6d5.scaleFromCenter,scaleMode:{originalHeight:_6d8.h,originalWidth:_6d8.w},scaleContent:_6d5.scaleContent,restoreAfterFinish:true}),new v.Opacity(_6d4,{sync:true,to:0})],_6d5); +}; +MochiKit.Visual.blindUp=function(_6dc,_6dd){ +var d=MochiKit.DOM; +var s=MochiKit.Style; +_6dc=d.getElement(_6dc); +var _6e0=s.getElementDimensions(_6dc,true); +var _6e1=s.makeClipping(_6dc); +_6dd=MochiKit.Base.update({scaleContent:false,scaleX:false,scaleMode:{originalHeight:_6e0.h,originalWidth:_6e0.w},restoreAfterFinish:true,afterFinishInternal:function(_6e2){ +s.hideElement(_6e2.element); +s.undoClipping(_6e2.element,_6e1); +}},_6dd); +return new MochiKit.Visual.Scale(_6dc,0,_6dd); +}; +MochiKit.Visual.blindDown=function(_6e3,_6e4){ +var d=MochiKit.DOM; +var s=MochiKit.Style; +_6e3=d.getElement(_6e3); +var _6e7=s.getElementDimensions(_6e3,true); +var _6e8; +_6e4=MochiKit.Base.update({scaleContent:false,scaleX:false,scaleFrom:0,scaleMode:{originalHeight:_6e7.h,originalWidth:_6e7.w},restoreAfterFinish:true,afterSetupInternal:function(_6e9){ +_6e8=s.makeClipping(_6e9.element); +s.setStyle(_6e9.element,{height:"0px"}); +s.showElement(_6e9.element); +},afterFinishInternal:function(_6ea){ +s.undoClipping(_6ea.element,_6e8); +}},_6e4); +return new MochiKit.Visual.Scale(_6e3,100,_6e4); +}; +MochiKit.Visual.switchOff=function(_6eb,_6ec){ +var d=MochiKit.DOM; +var s=MochiKit.Style; +_6eb=d.getElement(_6eb); +var _6ef=s.getElementDimensions(_6eb,true); +var _6f0=s.getStyle(_6eb,"opacity"); +var _6f1; +_6ec=MochiKit.Base.update({duration:0.7,restoreAfterFinish:true,beforeSetupInternal:function(_6f2){ +s.makePositioned(_6eb); +_6f1=s.makeClipping(_6eb); +},afterFinishInternal:function(_6f3){ +s.hideElement(_6eb); +s.undoClipping(_6eb,_6f1); +s.undoPositioned(_6eb); +s.setStyle(_6eb,{"opacity":_6f0}); +}},_6ec); +var v=MochiKit.Visual; +return new v.Sequence([new v.appear(_6eb,{sync:true,duration:0.57*_6ec.duration,from:0,transition:v.Transitions.flicker}),new v.Scale(_6eb,1,{sync:true,duration:0.43*_6ec.duration,scaleFromCenter:true,scaleX:false,scaleMode:{originalHeight:_6ef.h,originalWidth:_6ef.w},scaleContent:false,restoreAfterFinish:true})],_6ec); +}; +MochiKit.Visual.dropOut=function(_6f5,_6f6){ +var d=MochiKit.DOM; +var s=MochiKit.Style; +_6f5=d.getElement(_6f5); +var _6f9={top:s.getStyle(_6f5,"top"),left:s.getStyle(_6f5,"left"),opacity:s.getStyle(_6f5,"opacity")}; +_6f6=MochiKit.Base.update({duration:0.5,distance:100,beforeSetupInternal:function(_6fa){ +s.makePositioned(_6fa.effects[0].element); +},afterFinishInternal:function(_6fb){ +s.hideElement(_6fb.effects[0].element); +s.undoPositioned(_6fb.effects[0].element); +s.setStyle(_6fb.effects[0].element,_6f9); +}},_6f6); +var v=MochiKit.Visual; +return new v.Parallel([new v.Move(_6f5,{x:0,y:_6f6.distance,sync:true}),new v.Opacity(_6f5,{sync:true,to:0})],_6f6); +}; +MochiKit.Visual.shake=function(_6fd,_6fe){ +var d=MochiKit.DOM; +var v=MochiKit.Visual; +var s=MochiKit.Style; +_6fd=d.getElement(_6fd); +var _702={top:s.getStyle(_6fd,"top"),left:s.getStyle(_6fd,"left")}; +_6fe=MochiKit.Base.update({duration:0.5,afterFinishInternal:function(_703){ +s.undoPositioned(_6fd); +s.setStyle(_6fd,_702); +}},_6fe); +return new v.Sequence([new v.Move(_6fd,{sync:true,duration:0.1*_6fe.duration,x:20,y:0}),new v.Move(_6fd,{sync:true,duration:0.2*_6fe.duration,x:-40,y:0}),new v.Move(_6fd,{sync:true,duration:0.2*_6fe.duration,x:40,y:0}),new v.Move(_6fd,{sync:true,duration:0.2*_6fe.duration,x:-40,y:0}),new v.Move(_6fd,{sync:true,duration:0.2*_6fe.duration,x:40,y:0}),new v.Move(_6fd,{sync:true,duration:0.1*_6fe.duration,x:-20,y:0})],_6fe); +}; +MochiKit.Visual.slideDown=function(_704,_705){ +var d=MochiKit.DOM; +var b=MochiKit.Base; +var s=MochiKit.Style; +_704=d.getElement(_704); +if(!_704.firstChild){ +throw new Error("MochiKit.Visual.slideDown must be used on a element with a child"); +} +d.removeEmptyTextNodes(_704); +var _709=s.getStyle(_704.firstChild,"bottom")||0; +var _70a=s.getElementDimensions(_704,true); +var _70b; +_705=b.update({scaleContent:false,scaleX:false,scaleFrom:0,scaleMode:{originalHeight:_70a.h,originalWidth:_70a.w},restoreAfterFinish:true,afterSetupInternal:function(_70c){ +s.makePositioned(_70c.element); +s.makePositioned(_70c.element.firstChild); +if(/Opera/.test(navigator.userAgent)){ +s.setStyle(_70c.element,{top:""}); +} +_70b=s.makeClipping(_70c.element); +s.setStyle(_70c.element,{height:"0px"}); +s.showElement(_70c.element); +},afterUpdateInternal:function(_70d){ +var _70e=s.getElementDimensions(_70d.element,true); +s.setStyle(_70d.element.firstChild,{bottom:(_70d.dims[0]-_70e.h)+"px"}); +},afterFinishInternal:function(_70f){ +s.undoClipping(_70f.element,_70b); +if(/MSIE/.test(navigator.userAgent)){ +s.undoPositioned(_70f.element); +s.undoPositioned(_70f.element.firstChild); +}else{ +s.undoPositioned(_70f.element.firstChild); +s.undoPositioned(_70f.element); +} +s.setStyle(_70f.element.firstChild,{bottom:_709}); +}},_705); +return new MochiKit.Visual.Scale(_704,100,_705); +}; +MochiKit.Visual.slideUp=function(_710,_711){ +var d=MochiKit.DOM; +var b=MochiKit.Base; +var s=MochiKit.Style; +_710=d.getElement(_710); +if(!_710.firstChild){ +throw new Error("MochiKit.Visual.slideUp must be used on a element with a child"); +} +d.removeEmptyTextNodes(_710); +var _715=s.getStyle(_710.firstChild,"bottom"); +var _716=s.getElementDimensions(_710,true); +var _717; +_711=b.update({scaleContent:false,scaleX:false,scaleMode:{originalHeight:_716.h,originalWidth:_716.w},scaleFrom:100,restoreAfterFinish:true,beforeStartInternal:function(_718){ +s.makePositioned(_718.element); +s.makePositioned(_718.element.firstChild); +if(/Opera/.test(navigator.userAgent)){ +s.setStyle(_718.element,{top:""}); +} +_717=s.makeClipping(_718.element); +s.showElement(_718.element); +},afterUpdateInternal:function(_719){ +var _71a=s.getElementDimensions(_719.element,true); +s.setStyle(_719.element.firstChild,{bottom:(_719.dims[0]-_71a.h)+"px"}); +},afterFinishInternal:function(_71b){ +s.hideElement(_71b.element); +s.undoClipping(_71b.element,_717); +s.undoPositioned(_71b.element.firstChild); +s.undoPositioned(_71b.element); +s.setStyle(_71b.element.firstChild,{bottom:_715}); +}},_711); +return new MochiKit.Visual.Scale(_710,0,_711); +}; +MochiKit.Visual.squish=function(_71c,_71d){ +var d=MochiKit.DOM; +var b=MochiKit.Base; +var s=MochiKit.Style; +var _721=s.getElementDimensions(_71c,true); +var _722; +_71d=b.update({restoreAfterFinish:true,scaleMode:{originalHeight:_721.w,originalWidth:_721.h},beforeSetupInternal:function(_723){ +_722=s.makeClipping(_723.element); +},afterFinishInternal:function(_724){ +s.hideElement(_724.element); +s.undoClipping(_724.element,_722); +}},_71d); +return new MochiKit.Visual.Scale(_71c,/Opera/.test(navigator.userAgent)?1:0,_71d); +}; +MochiKit.Visual.grow=function(_725,_726){ +var d=MochiKit.DOM; +var v=MochiKit.Visual; +var s=MochiKit.Style; +_725=d.getElement(_725); +_726=MochiKit.Base.update({direction:"center",moveTransition:v.Transitions.sinoidal,scaleTransition:v.Transitions.sinoidal,opacityTransition:v.Transitions.full,scaleContent:true,scaleFromCenter:false},_726); +var _72a={top:_725.style.top,left:_725.style.left,height:_725.style.height,width:_725.style.width,opacity:s.getStyle(_725,"opacity")}; +var dims=s.getElementDimensions(_725,true); +var _72c,_72d; +var _72e,_72f; +switch(_726.direction){ +case "top-left": +_72c=_72d=_72e=_72f=0; +break; +case "top-right": +_72c=dims.w; +_72d=_72f=0; +_72e=-dims.w; +break; +case "bottom-left": +_72c=_72e=0; +_72d=dims.h; +_72f=-dims.h; +break; +case "bottom-right": +_72c=dims.w; +_72d=dims.h; +_72e=-dims.w; +_72f=-dims.h; +break; +case "center": +_72c=dims.w/2; +_72d=dims.h/2; +_72e=-dims.w/2; +_72f=-dims.h/2; +break; +} +var _730=MochiKit.Base.update({beforeSetupInternal:function(_731){ +s.setStyle(_731.effects[0].element,{height:"0px"}); +s.showElement(_731.effects[0].element); +},afterFinishInternal:function(_732){ +s.undoClipping(_732.effects[0].element); +s.undoPositioned(_732.effects[0].element); +s.setStyle(_732.effects[0].element,_72a); +}},_726); +return new v.Move(_725,{x:_72c,y:_72d,duration:0.01,beforeSetupInternal:function(_733){ +s.hideElement(_733.element); +s.makeClipping(_733.element); +s.makePositioned(_733.element); +},afterFinishInternal:function(_734){ +new v.Parallel([new v.Opacity(_734.element,{sync:true,to:1,from:0,transition:_726.opacityTransition}),new v.Move(_734.element,{x:_72e,y:_72f,sync:true,transition:_726.moveTransition}),new v.Scale(_734.element,100,{scaleMode:{originalHeight:dims.h,originalWidth:dims.w},sync:true,scaleFrom:/Opera/.test(navigator.userAgent)?1:0,transition:_726.scaleTransition,scaleContent:_726.scaleContent,scaleFromCenter:_726.scaleFromCenter,restoreAfterFinish:true})],_730); +}}); +}; +MochiKit.Visual.shrink=function(_735,_736){ +var d=MochiKit.DOM; +var v=MochiKit.Visual; +var s=MochiKit.Style; +_735=d.getElement(_735); +_736=MochiKit.Base.update({direction:"center",moveTransition:v.Transitions.sinoidal,scaleTransition:v.Transitions.sinoidal,opacityTransition:v.Transitions.none,scaleContent:true,scaleFromCenter:false},_736); +var _73a={top:_735.style.top,left:_735.style.left,height:_735.style.height,width:_735.style.width,opacity:s.getStyle(_735,"opacity")}; +var dims=s.getElementDimensions(_735,true); +var _73c,_73d; +switch(_736.direction){ +case "top-left": +_73c=_73d=0; +break; +case "top-right": +_73c=dims.w; +_73d=0; +break; +case "bottom-left": +_73c=0; +_73d=dims.h; +break; +case "bottom-right": +_73c=dims.w; +_73d=dims.h; +break; +case "center": +_73c=dims.w/2; +_73d=dims.h/2; +break; +} +var _73e; +var _73f=MochiKit.Base.update({beforeStartInternal:function(_740){ +s.makePositioned(_740.effects[0].element); +_73e=s.makeClipping(_740.effects[0].element); +},afterFinishInternal:function(_741){ +s.hideElement(_741.effects[0].element); +s.undoClipping(_741.effects[0].element,_73e); +s.undoPositioned(_741.effects[0].element); +s.setStyle(_741.effects[0].element,_73a); +}},_736); +return new v.Parallel([new v.Opacity(_735,{sync:true,to:0,from:1,transition:_736.opacityTransition}),new v.Scale(_735,/Opera/.test(navigator.userAgent)?1:0,{scaleMode:{originalHeight:dims.h,originalWidth:dims.w},sync:true,transition:_736.scaleTransition,scaleContent:_736.scaleContent,scaleFromCenter:_736.scaleFromCenter,restoreAfterFinish:true}),new v.Move(_735,{x:_73c,y:_73d,sync:true,transition:_736.moveTransition})],_73f); +}; +MochiKit.Visual.pulsate=function(_742,_743){ +var d=MochiKit.DOM; +var v=MochiKit.Visual; +var b=MochiKit.Base; +var _747=MochiKit.Style.getStyle(_742,"opacity"); +_743=b.update({duration:3,from:0,afterFinishInternal:function(_748){ +MochiKit.Style.setStyle(_748.element,{"opacity":_747}); +}},_743); +var _749=_743.transition||v.Transitions.sinoidal; +_743.transition=function(pos){ +return _749(1-v.Transitions.pulse(pos,_743.pulses)); +}; +return new v.Opacity(_742,_743); +}; +MochiKit.Visual.fold=function(_74b,_74c){ +var d=MochiKit.DOM; +var v=MochiKit.Visual; +var s=MochiKit.Style; +_74b=d.getElement(_74b); +var _750=s.getElementDimensions(_74b,true); +var _751={top:_74b.style.top,left:_74b.style.left,width:_74b.style.width,height:_74b.style.height}; +var _752=s.makeClipping(_74b); +_74c=MochiKit.Base.update({scaleContent:false,scaleX:false,scaleMode:{originalHeight:_750.h,originalWidth:_750.w},afterFinishInternal:function(_753){ +new v.Scale(_74b,1,{scaleContent:false,scaleY:false,scaleMode:{originalHeight:_750.h,originalWidth:_750.w},afterFinishInternal:function(_754){ +s.hideElement(_754.element); +s.undoClipping(_754.element,_752); +s.setStyle(_754.element,_751); +}}); +}},_74c); +return new v.Scale(_74b,5,_74c); +}; +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","tagifyText","multiple","toggle","Parallel","Sequence","Opacity","Move","Scale","Highlight","ScrollTo","Morph","fade","appear","puff","blindUp","blindDown","switchOff","dropOut","shake","slideDown","slideUp","squish","grow","shrink","pulsate","fold"]; +MochiKit.Visual.EXPORT_OK=["Base","PAIRS"]; +MochiKit.Visual.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.Visual); +MochiKit.Base._deps("DragAndDrop",["Base","Iter","DOM","Signal","Visual","Position"]); +MochiKit.DragAndDrop.NAME="MochiKit.DragAndDrop"; +MochiKit.DragAndDrop.VERSION="1.4.2"; +MochiKit.DragAndDrop.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.DragAndDrop.toString=function(){ +return this.__repr__(); +}; +MochiKit.DragAndDrop.EXPORT=["Droppable","Draggable"]; +MochiKit.DragAndDrop.EXPORT_OK=["Droppables","Draggables"]; +MochiKit.DragAndDrop.Droppables={drops:[],remove:function(_756){ +this.drops=MochiKit.Base.filter(function(d){ +return d.element!=MochiKit.DOM.getElement(_756); +},this.drops); +},register:function(drop){ +this.drops.push(drop); +},unregister:function(drop){ +this.drops=MochiKit.Base.filter(function(d){ +return d!=drop; +},this.drops); +},prepare:function(_75b){ +MochiKit.Base.map(function(drop){ +if(drop.isAccepted(_75b)){ +if(drop.options.activeclass){ +MochiKit.DOM.addElementClass(drop.element,drop.options.activeclass); +} +drop.options.onactive(drop.element,_75b); +} +},this.drops); +},findDeepestChild:function(_75d){ +deepest=_75d[0]; +for(i=1;i<_75d.length;++i){ +if(MochiKit.DOM.isChildNode(_75d[i].element,deepest.element)){ +deepest=_75d[i]; +} +} +return deepest; +},show:function(_75e,_75f){ +if(!this.drops.length){ +return; +} +var _760=[]; +if(this.last_active){ +this.last_active.deactivate(); +} +MochiKit.Iter.forEach(this.drops,function(drop){ +if(drop.isAffected(_75e,_75f)){ +_760.push(drop); +} +}); +if(_760.length>0){ +drop=this.findDeepestChild(_760); +MochiKit.Position.within(drop.element,_75e.page.x,_75e.page.y); +drop.options.onhover(_75f,drop.element,MochiKit.Position.overlap(drop.options.overlap,drop.element)); +drop.activate(); +} +},fire:function(_762,_763){ +if(!this.last_active){ +return; +} +MochiKit.Position.prepare(); +if(this.last_active.isAffected(_762.mouse(),_763)){ +this.last_active.options.ondrop(_763,this.last_active.element,_762); +} +},reset:function(_764){ +MochiKit.Base.map(function(drop){ +if(drop.options.activeclass){ +MochiKit.DOM.removeElementClass(drop.element,drop.options.activeclass); +} +drop.options.ondesactive(drop.element,_764); +},this.drops); +if(this.last_active){ +this.last_active.deactivate(); +} +}}; +MochiKit.DragAndDrop.Droppable=function(_766,_767){ +var cls=arguments.callee; +if(!(this instanceof cls)){ +return new cls(_766,_767); +} +this.__init__(_766,_767); +}; +MochiKit.DragAndDrop.Droppable.prototype={__class__:MochiKit.DragAndDrop.Droppable,__init__:function(_769,_76a){ +var d=MochiKit.DOM; +var b=MochiKit.Base; +this.element=d.getElement(_769); +this.options=b.update({greedy:true,hoverclass:null,activeclass:null,hoverfunc:b.noop,accept:null,onactive:b.noop,ondesactive:b.noop,onhover:b.noop,ondrop:b.noop,containment:[],tree:false},_76a); +this.options._containers=[]; +b.map(MochiKit.Base.bind(function(c){ +this.options._containers.push(d.getElement(c)); +},this),this.options.containment); +MochiKit.Style.makePositioned(this.element); +MochiKit.DragAndDrop.Droppables.register(this); +},isContained:function(_76e){ +if(this.options._containers.length){ +var _76f; +if(this.options.tree){ +_76f=_76e.treeNode; +}else{ +_76f=_76e.parentNode; +} +return MochiKit.Iter.some(this.options._containers,function(c){ +return _76f==c; +}); +}else{ +return true; +} +},isAccepted:function(_771){ +return ((!this.options.accept)||MochiKit.Iter.some(this.options.accept,function(c){ +return MochiKit.DOM.hasElementClass(_771,c); +})); +},isAffected:function(_773,_774){ +return ((this.element!=_774)&&this.isContained(_774)&&this.isAccepted(_774)&&MochiKit.Position.within(this.element,_773.page.x,_773.page.y)); +},deactivate:function(){ +if(this.options.hoverclass){ +MochiKit.DOM.removeElementClass(this.element,this.options.hoverclass); +} +this.options.hoverfunc(this.element,false); +MochiKit.DragAndDrop.Droppables.last_active=null; +},activate:function(){ +if(this.options.hoverclass){ +MochiKit.DOM.addElementClass(this.element,this.options.hoverclass); +} +this.options.hoverfunc(this.element,true); +MochiKit.DragAndDrop.Droppables.last_active=this; +},destroy:function(){ +MochiKit.DragAndDrop.Droppables.unregister(this); +},repr:function(){ +return "["+this.__class__.NAME+", options:"+MochiKit.Base.repr(this.options)+"]"; +}}; +MochiKit.DragAndDrop.Draggables={drags:[],register:function(_775){ +if(this.drags.length===0){ +var conn=MochiKit.Signal.connect; +this.eventMouseUp=conn(document,"onmouseup",this,this.endDrag); +this.eventMouseMove=conn(document,"onmousemove",this,this.updateDrag); +this.eventKeypress=conn(document,"onkeypress",this,this.keyPress); +} +this.drags.push(_775); +},unregister:function(_777){ +this.drags=MochiKit.Base.filter(function(d){ +return d!=_777; +},this.drags); +if(this.drags.length===0){ +var disc=MochiKit.Signal.disconnect; +disc(this.eventMouseUp); +disc(this.eventMouseMove); +disc(this.eventKeypress); +} +},activate:function(_77a){ +window.focus(); +this.activeDraggable=_77a; +},deactivate:function(){ +this.activeDraggable=null; +},updateDrag:function(_77b){ +if(!this.activeDraggable){ +return; +} +var _77c=_77b.mouse(); +if(this._lastPointer&&(MochiKit.Base.repr(this._lastPointer.page)==MochiKit.Base.repr(_77c.page))){ +return; +} +this._lastPointer=_77c; +this.activeDraggable.updateDrag(_77b,_77c); +},endDrag:function(_77d){ +if(!this.activeDraggable){ +return; +} +this._lastPointer=null; +this.activeDraggable.endDrag(_77d); +this.activeDraggable=null; +},keyPress:function(_77e){ +if(this.activeDraggable){ +this.activeDraggable.keyPress(_77e); +} +},notify:function(_77f,_780,_781){ +MochiKit.Signal.signal(this,_77f,_780,_781); +}}; +MochiKit.DragAndDrop.Draggable=function(_782,_783){ +var cls=arguments.callee; +if(!(this instanceof cls)){ +return new cls(_782,_783); +} +this.__init__(_782,_783); +}; +MochiKit.DragAndDrop.Draggable.prototype={__class__:MochiKit.DragAndDrop.Draggable,__init__:function(_785,_786){ +var v=MochiKit.Visual; +var b=MochiKit.Base; +_786=b.update({handle:false,starteffect:function(_789){ +this._savedOpacity=MochiKit.Style.getStyle(_789,"opacity")||1; +new v.Opacity(_789,{duration:0.2,from:this._savedOpacity,to:0.7}); +},reverteffect:function(_78a,_78b,_78c){ +var dur=Math.sqrt(Math.abs(_78b^2)+Math.abs(_78c^2))*0.02; +return new v.Move(_78a,{x:-_78c,y:-_78b,duration:dur}); +},endeffect:function(_78e){ +new v.Opacity(_78e,{duration:0.2,from:0.7,to:this._savedOpacity}); +},onchange:b.noop,zindex:1000,revert:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,snap:false},_786); +var d=MochiKit.DOM; +this.element=d.getElement(_785); +if(_786.handle&&(typeof (_786.handle)=="string")){ +this.handle=d.getFirstElementByTagAndClassName(null,_786.handle,this.element); +} +if(!this.handle){ +this.handle=d.getElement(_786.handle); +} +if(!this.handle){ +this.handle=this.element; +} +if(_786.scroll&&!_786.scroll.scrollTo&&!_786.scroll.outerHTML){ +_786.scroll=d.getElement(_786.scroll); +this._isScrollChild=MochiKit.DOM.isChildNode(this.element,_786.scroll); +} +MochiKit.Style.makePositioned(this.element); +this.delta=this.currentDelta(); +this.options=_786; +this.dragging=false; +this.eventMouseDown=MochiKit.Signal.connect(this.handle,"onmousedown",this,this.initDrag); +MochiKit.DragAndDrop.Draggables.register(this); +},destroy:function(){ +MochiKit.Signal.disconnect(this.eventMouseDown); +MochiKit.DragAndDrop.Draggables.unregister(this); +},currentDelta:function(){ +var s=MochiKit.Style.getStyle; +return [parseInt(s(this.element,"left")||"0"),parseInt(s(this.element,"top")||"0")]; +},initDrag:function(_791){ +if(!_791.mouse().button.left){ +return; +} +var src=_791.target(); +var _793=(src.tagName||"").toUpperCase(); +if(_793==="INPUT"||_793==="SELECT"||_793==="OPTION"||_793==="BUTTON"||_793==="TEXTAREA"){ +return; +} +if(this._revert){ +this._revert.cancel(); +this._revert=null; +} +var _794=_791.mouse(); +var pos=MochiKit.Position.cumulativeOffset(this.element); +this.offset=[_794.page.x-pos.x,_794.page.y-pos.y]; +MochiKit.DragAndDrop.Draggables.activate(this); +_791.stop(); +},startDrag:function(_796){ +this.dragging=true; +if(this.options.selectclass){ +MochiKit.DOM.addElementClass(this.element,this.options.selectclass); +} +if(this.options.zindex){ +this.originalZ=parseInt(MochiKit.Style.getStyle(this.element,"z-index")||"0"); +this.element.style.zIndex=this.options.zindex; +} +if(this.options.ghosting){ +this._clone=this.element.cloneNode(true); +this.ghostPosition=MochiKit.Position.absolutize(this.element); +this.element.parentNode.insertBefore(this._clone,this.element); +} +if(this.options.scroll){ +if(this.options.scroll==window){ +var _797=this._getWindowScroll(this.options.scroll); +this.originalScrollLeft=_797.left; +this.originalScrollTop=_797.top; +}else{ +this.originalScrollLeft=this.options.scroll.scrollLeft; +this.originalScrollTop=this.options.scroll.scrollTop; +} +} +MochiKit.DragAndDrop.Droppables.prepare(this.element); +MochiKit.DragAndDrop.Draggables.notify("start",this,_796); +if(this.options.starteffect){ +this.options.starteffect(this.element); +} +},updateDrag:function(_798,_799){ +if(!this.dragging){ +this.startDrag(_798); +} +MochiKit.Position.prepare(); +MochiKit.DragAndDrop.Droppables.show(_799,this.element); +MochiKit.DragAndDrop.Draggables.notify("drag",this,_798); +this.draw(_799); +this.options.onchange(this); +if(this.options.scroll){ +this.stopScrolling(); +var p,q; +if(this.options.scroll==window){ +var s=this._getWindowScroll(this.options.scroll); +p=new MochiKit.Style.Coordinates(s.left,s.top); +q=new MochiKit.Style.Coordinates(s.left+s.width,s.top+s.height); +}else{ +p=MochiKit.Position.page(this.options.scroll); +p.x+=this.options.scroll.scrollLeft; +p.y+=this.options.scroll.scrollTop; +p.x+=(window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0); +p.y+=(window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0); +q=new MochiKit.Style.Coordinates(p.x+this.options.scroll.offsetWidth,p.y+this.options.scroll.offsetHeight); +} +var _79d=[0,0]; +if(_799.page.x>(q.x-this.options.scrollSensitivity)){ +_79d[0]=_799.page.x-(q.x-this.options.scrollSensitivity); +}else{ +if(_799.page.x<(p.x+this.options.scrollSensitivity)){ +_79d[0]=_799.page.x-(p.x+this.options.scrollSensitivity); +} +} +if(_799.page.y>(q.y-this.options.scrollSensitivity)){ +_79d[1]=_799.page.y-(q.y-this.options.scrollSensitivity); +}else{ +if(_799.page.y<(p.y+this.options.scrollSensitivity)){ +_79d[1]=_799.page.y-(p.y+this.options.scrollSensitivity); +} +} +this.startScrolling(_79d); +} +if(/AppleWebKit/.test(navigator.appVersion)){ +window.scrollBy(0,0); +} +_798.stop(); +},finishDrag:function(_79e,_79f){ +var dr=MochiKit.DragAndDrop; +this.dragging=false; +if(this.options.selectclass){ +MochiKit.DOM.removeElementClass(this.element,this.options.selectclass); +} +if(this.options.ghosting){ +MochiKit.Position.relativize(this.element,this.ghostPosition); +MochiKit.DOM.removeElement(this._clone); +this._clone=null; +} +if(_79f){ +dr.Droppables.fire(_79e,this.element); +} +dr.Draggables.notify("end",this,_79e); +var _7a1=this.options.revert; +if(_7a1&&typeof (_7a1)=="function"){ +_7a1=_7a1(this.element); +} +var d=this.currentDelta(); +if(_7a1&&this.options.reverteffect){ +this._revert=this.options.reverteffect(this.element,d[1]-this.delta[1],d[0]-this.delta[0]); +}else{ +this.delta=d; +} +if(this.options.zindex){ +this.element.style.zIndex=this.originalZ; +} +if(this.options.endeffect){ +this.options.endeffect(this.element); +} +dr.Draggables.deactivate(); +dr.Droppables.reset(this.element); +},keyPress:function(_7a3){ +if(_7a3.key().string!="KEY_ESCAPE"){ +return; +} +this.finishDrag(_7a3,false); +_7a3.stop(); +},endDrag:function(_7a4){ +if(!this.dragging){ +return; +} +this.stopScrolling(); +this.finishDrag(_7a4,true); +_7a4.stop(); +},draw:function(_7a5){ +var pos=MochiKit.Position.cumulativeOffset(this.element); +var d=this.currentDelta(); +pos.x-=d[0]; +pos.y-=d[1]; +if(this.options.scroll&&(this.options.scroll!=window&&this._isScrollChild)){ +pos.x-=this.options.scroll.scrollLeft-this.originalScrollLeft; +pos.y-=this.options.scroll.scrollTop-this.originalScrollTop; +} +var p=[_7a5.page.x-pos.x-this.offset[0],_7a5.page.y-pos.y-this.offset[1]]; +if(this.options.snap){ +if(typeof (this.options.snap)=="function"){ +p=this.options.snap(p[0],p[1]); +}else{ +if(this.options.snap instanceof Array){ +var i=-1; +p=MochiKit.Base.map(MochiKit.Base.bind(function(v){ +i+=1; +return Math.round(v/this.options.snap[i])*this.options.snap[i]; +},this),p); +}else{ +p=MochiKit.Base.map(MochiKit.Base.bind(function(v){ +return Math.round(v/this.options.snap)*this.options.snap; +},this),p); +} +} +} +var _7ac=this.element.style; +if((!this.options.constraint)||(this.options.constraint=="horizontal")){ +_7ac.left=p[0]+"px"; +} +if((!this.options.constraint)||(this.options.constraint=="vertical")){ +_7ac.top=p[1]+"px"; +} +if(_7ac.visibility=="hidden"){ +_7ac.visibility=""; +} +},stopScrolling:function(){ +if(this.scrollInterval){ +clearInterval(this.scrollInterval); +this.scrollInterval=null; +MochiKit.DragAndDrop.Draggables._lastScrollPointer=null; +} +},startScrolling:function(_7ad){ +if(!_7ad[0]&&!_7ad[1]){ +return; +} +this.scrollSpeed=[_7ad[0]*this.options.scrollSpeed,_7ad[1]*this.options.scrollSpeed]; +this.lastScrolled=new Date(); +this.scrollInterval=setInterval(MochiKit.Base.bind(this.scroll,this),10); +},scroll:function(){ +var _7ae=new Date(); +var _7af=_7ae-this.lastScrolled; +this.lastScrolled=_7ae; +if(this.options.scroll==window){ +var s=this._getWindowScroll(this.options.scroll); +if(this.scrollSpeed[0]||this.scrollSpeed[1]){ +var dm=_7af/1000; +this.options.scroll.scrollTo(s.left+dm*this.scrollSpeed[0],s.top+dm*this.scrollSpeed[1]); +} +}else{ +this.options.scroll.scrollLeft+=this.scrollSpeed[0]*_7af/1000; +this.options.scroll.scrollTop+=this.scrollSpeed[1]*_7af/1000; +} +var d=MochiKit.DragAndDrop; +MochiKit.Position.prepare(); +d.Droppables.show(d.Draggables._lastPointer,this.element); +d.Draggables.notify("drag",this); +if(this._isScrollChild){ +d.Draggables._lastScrollPointer=d.Draggables._lastScrollPointer||d.Draggables._lastPointer; +d.Draggables._lastScrollPointer.x+=this.scrollSpeed[0]*_7af/1000; +d.Draggables._lastScrollPointer.y+=this.scrollSpeed[1]*_7af/1000; +if(d.Draggables._lastScrollPointer.x<0){ +d.Draggables._lastScrollPointer.x=0; +} +if(d.Draggables._lastScrollPointer.y<0){ +d.Draggables._lastScrollPointer.y=0; +} +this.draw(d.Draggables._lastScrollPointer); +} +this.options.onchange(this); +},_getWindowScroll:function(win){ +var vp,w,h; +MochiKit.DOM.withWindow(win,function(){ +vp=MochiKit.Style.getViewportPosition(win.document); +}); +if(win.innerWidth){ +w=win.innerWidth; +h=win.innerHeight; +}else{ +if(win.document.documentElement&&win.document.documentElement.clientWidth){ +w=win.document.documentElement.clientWidth; +h=win.document.documentElement.clientHeight; +}else{ +w=win.document.body.offsetWidth; +h=win.document.body.offsetHeight; +} +} +return {top:vp.y,left:vp.x,width:w,height:h}; +},repr:function(){ +return "["+this.__class__.NAME+", options:"+MochiKit.Base.repr(this.options)+"]"; +}}; +MochiKit.DragAndDrop.__new__=function(){ +MochiKit.Base.nameFunctions(this); +this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)}; +}; +MochiKit.DragAndDrop.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.DragAndDrop); +MochiKit.Base._deps("Sortable",["Base","Iter","DOM","Position","DragAndDrop"]); +MochiKit.Sortable.NAME="MochiKit.Sortable"; +MochiKit.Sortable.VERSION="1.4.2"; +MochiKit.Sortable.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.Sortable.toString=function(){ +return this.__repr__(); +}; +MochiKit.Sortable.EXPORT=[]; +MochiKit.Sortable.EXPORT_OK=[]; +MochiKit.Base.update(MochiKit.Sortable,{sortables:{},_findRootElement:function(_7b7){ +while(_7b7.tagName.toUpperCase()!="BODY"){ +if(_7b7.id&&MochiKit.Sortable.sortables[_7b7.id]){ +return _7b7; +} +_7b7=_7b7.parentNode; +} +},_createElementId:function(_7b8){ +if(_7b8.id==null||_7b8.id==""){ +var d=MochiKit.DOM; +var id; +var _7bb=1; +while(d.getElement(id="sortable"+_7bb)!=null){ +_7bb+=1; +} +d.setNodeAttribute(_7b8,"id",id); +} +},options:function(_7bc){ +_7bc=MochiKit.Sortable._findRootElement(MochiKit.DOM.getElement(_7bc)); +if(!_7bc){ +return; +} +return MochiKit.Sortable.sortables[_7bc.id]; +},destroy:function(_7bd){ +var s=MochiKit.Sortable.options(_7bd); +var b=MochiKit.Base; +var d=MochiKit.DragAndDrop; +if(s){ +MochiKit.Signal.disconnect(s.startHandle); +MochiKit.Signal.disconnect(s.endHandle); +b.map(function(dr){ +d.Droppables.remove(dr); +},s.droppables); +b.map(function(dr){ +dr.destroy(); +},s.draggables); +delete MochiKit.Sortable.sortables[s.element.id]; +} +},create:function(_7c3,_7c4){ +_7c3=MochiKit.DOM.getElement(_7c3); +var self=MochiKit.Sortable; +self._createElementId(_7c3); +_7c4=MochiKit.Base.update({element:_7c3,tag:"li",dropOnEmpty:false,tree:false,treeTag:"ul",overlap:"vertical",constraint:"vertical",containment:[_7c3],handle:false,only:false,hoverclass:null,ghosting:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,format:/^[^_]*_(.*)$/,onChange:MochiKit.Base.noop,onUpdate:MochiKit.Base.noop,accept:null},_7c4); +self.destroy(_7c3); +var _7c6={revert:true,ghosting:_7c4.ghosting,scroll:_7c4.scroll,scrollSensitivity:_7c4.scrollSensitivity,scrollSpeed:_7c4.scrollSpeed,constraint:_7c4.constraint,handle:_7c4.handle}; +if(_7c4.starteffect){ +_7c6.starteffect=_7c4.starteffect; +} +if(_7c4.reverteffect){ +_7c6.reverteffect=_7c4.reverteffect; +}else{ +if(_7c4.ghosting){ +_7c6.reverteffect=function(_7c7){ +_7c7.style.top=0; +_7c7.style.left=0; +}; +} +} +if(_7c4.endeffect){ +_7c6.endeffect=_7c4.endeffect; +} +if(_7c4.zindex){ +_7c6.zindex=_7c4.zindex; +} +var _7c8={overlap:_7c4.overlap,containment:_7c4.containment,hoverclass:_7c4.hoverclass,onhover:self.onHover,tree:_7c4.tree,accept:_7c4.accept}; +var _7c9={onhover:self.onEmptyHover,overlap:_7c4.overlap,containment:_7c4.containment,hoverclass:_7c4.hoverclass,accept:_7c4.accept}; +MochiKit.DOM.removeEmptyTextNodes(_7c3); +_7c4.draggables=[]; +_7c4.droppables=[]; +if(_7c4.dropOnEmpty||_7c4.tree){ +new MochiKit.DragAndDrop.Droppable(_7c3,_7c9); +_7c4.droppables.push(_7c3); +} +MochiKit.Base.map(function(e){ +var _7cb=_7c4.handle?MochiKit.DOM.getFirstElementByTagAndClassName(null,_7c4.handle,e):e; +_7c4.draggables.push(new MochiKit.DragAndDrop.Draggable(e,MochiKit.Base.update(_7c6,{handle:_7cb}))); +new MochiKit.DragAndDrop.Droppable(e,_7c8); +if(_7c4.tree){ +e.treeNode=_7c3; +} +_7c4.droppables.push(e); +},(self.findElements(_7c3,_7c4)||[])); +if(_7c4.tree){ +MochiKit.Base.map(function(e){ +new MochiKit.DragAndDrop.Droppable(e,_7c9); +e.treeNode=_7c3; +_7c4.droppables.push(e); +},(self.findTreeElements(_7c3,_7c4)||[])); +} +self.sortables[_7c3.id]=_7c4; +_7c4.lastValue=self.serialize(_7c3); +_7c4.startHandle=MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables,"start",MochiKit.Base.partial(self.onStart,_7c3)); +_7c4.endHandle=MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables,"end",MochiKit.Base.partial(self.onEnd,_7c3)); +},onStart:function(_7cd,_7ce){ +var self=MochiKit.Sortable; +var _7d0=self.options(_7cd); +_7d0.lastValue=self.serialize(_7d0.element); +},onEnd:function(_7d1,_7d2){ +var self=MochiKit.Sortable; +self.unmark(); +var _7d4=self.options(_7d1); +if(_7d4.lastValue!=self.serialize(_7d4.element)){ +_7d4.onUpdate(_7d4.element); +} +},findElements:function(_7d5,_7d6){ +return MochiKit.Sortable.findChildren(_7d5,_7d6.only,_7d6.tree,_7d6.tag); +},findTreeElements:function(_7d7,_7d8){ +return MochiKit.Sortable.findChildren(_7d7,_7d8.only,_7d8.tree?true:false,_7d8.treeTag); +},findChildren:function(_7d9,only,_7db,_7dc){ +if(!_7d9.hasChildNodes()){ +return null; +} +_7dc=_7dc.toUpperCase(); +if(only){ +only=MochiKit.Base.flattenArray([only]); +} +var _7dd=[]; +MochiKit.Base.map(function(e){ +if(e.tagName&&e.tagName.toUpperCase()==_7dc&&(!only||MochiKit.Iter.some(only,function(c){ +return MochiKit.DOM.hasElementClass(e,c); +}))){ +_7dd.push(e); +} +if(_7db){ +var _7e0=MochiKit.Sortable.findChildren(e,only,_7db,_7dc); +if(_7e0&&_7e0.length>0){ +_7dd=_7dd.concat(_7e0); +} +} +},_7d9.childNodes); +return _7dd; +},onHover:function(_7e1,_7e2,_7e3){ +if(MochiKit.DOM.isChildNode(_7e2,_7e1)){ +return; +} +var self=MochiKit.Sortable; +if(_7e3>0.33&&_7e3<0.66&&self.options(_7e2).tree){ +return; +}else{ +if(_7e3>0.5){ +self.mark(_7e2,"before"); +if(_7e2.previousSibling!=_7e1){ +var _7e5=_7e1.parentNode; +_7e1.style.visibility="hidden"; +_7e2.parentNode.insertBefore(_7e1,_7e2); +if(_7e2.parentNode!=_7e5){ +self.options(_7e5).onChange(_7e1); +} +self.options(_7e2.parentNode).onChange(_7e1); +} +}else{ +self.mark(_7e2,"after"); +var _7e6=_7e2.nextSibling||null; +if(_7e6!=_7e1){ +var _7e5=_7e1.parentNode; +_7e1.style.visibility="hidden"; +_7e2.parentNode.insertBefore(_7e1,_7e6); +if(_7e2.parentNode!=_7e5){ +self.options(_7e5).onChange(_7e1); +} +self.options(_7e2.parentNode).onChange(_7e1); +} +} +} +},_offsetSize:function(_7e7,type){ +if(type=="vertical"||type=="height"){ +return _7e7.offsetHeight; +}else{ +return _7e7.offsetWidth; +} +},onEmptyHover:function(_7e9,_7ea,_7eb){ +var _7ec=_7e9.parentNode; +var self=MochiKit.Sortable; +var _7ee=self.options(_7ea); +if(!MochiKit.DOM.isChildNode(_7ea,_7e9)){ +var _7ef; +var _7f0=self.findElements(_7ea,{tag:_7ee.tag,only:_7ee.only}); +var _7f1=null; +if(_7f0){ +var _7f2=self._offsetSize(_7ea,_7ee.overlap)*(1-_7eb); +for(_7ef=0;_7ef<_7f0.length;_7ef+=1){ +if(_7f2-self._offsetSize(_7f0[_7ef],_7ee.overlap)>=0){ +_7f2-=self._offsetSize(_7f0[_7ef],_7ee.overlap); +}else{ +if(_7f2-(self._offsetSize(_7f0[_7ef],_7ee.overlap)/2)>=0){ +_7f1=_7ef+1<_7f0.length?_7f0[_7ef+1]:null; +break; +}else{ +_7f1=_7f0[_7ef]; +break; +} +} +} +} +_7ea.insertBefore(_7e9,_7f1); +self.options(_7ec).onChange(_7e9); +_7ee.onChange(_7e9); +} +},unmark:function(){ +var m=MochiKit.Sortable._marker; +if(m){ +MochiKit.Style.hideElement(m); +} +},mark:function(_7f4,_7f5){ +var d=MochiKit.DOM; +var self=MochiKit.Sortable; +var _7f8=self.options(_7f4.parentNode); +if(_7f8&&!_7f8.ghosting){ +return; +} +if(!self._marker){ +self._marker=d.getElement("dropmarker")||document.createElement("DIV"); +MochiKit.Style.hideElement(self._marker); +d.addElementClass(self._marker,"dropmarker"); +self._marker.style.position="absolute"; +document.getElementsByTagName("body").item(0).appendChild(self._marker); +} +var _7f9=MochiKit.Position.cumulativeOffset(_7f4); +self._marker.style.left=_7f9.x+"px"; +self._marker.style.top=_7f9.y+"px"; +if(_7f5=="after"){ +if(_7f8.overlap=="horizontal"){ +self._marker.style.left=(_7f9.x+_7f4.clientWidth)+"px"; +}else{ +self._marker.style.top=(_7f9.y+_7f4.clientHeight)+"px"; +} +} +MochiKit.Style.showElement(self._marker); +},_tree:function(_7fa,_7fb,_7fc){ +var self=MochiKit.Sortable; +var _7fe=self.findElements(_7fa,_7fb)||[]; +for(var i=0;i<_7fe.length;++i){ +var _800=_7fe[i].id.match(_7fb.format); +if(!_800){ +continue; +} +var _801={id:encodeURIComponent(_800?_800[1]:null),element:_7fa,parent:_7fc,children:[],position:_7fc.children.length,container:self._findChildrenElement(_7fe[i],_7fb.treeTag.toUpperCase())}; +if(_801.container){ +self._tree(_801.container,_7fb,_801); +} +_7fc.children.push(_801); +} +return _7fc; +},_findChildrenElement:function(_802,_803){ +if(_802&&_802.hasChildNodes){ +_803=_803.toUpperCase(); +for(var i=0;i<_802.childNodes.length;++i){ +if(_802.childNodes[i].tagName.toUpperCase()==_803){ +return _802.childNodes[i]; +} +} +} +return null; +},tree:function(_805,_806){ +_805=MochiKit.DOM.getElement(_805); +var _807=MochiKit.Sortable.options(_805); +_806=MochiKit.Base.update({tag:_807.tag,treeTag:_807.treeTag,only:_807.only,name:_805.id,format:_807.format},_806||{}); +var root={id:null,parent:null,children:new Array,container:_805,position:0}; +return MochiKit.Sortable._tree(_805,_806,root); +},setSequence:function(_809,_80a,_80b){ +var self=MochiKit.Sortable; +var b=MochiKit.Base; +_809=MochiKit.DOM.getElement(_809); +_80b=b.update(self.options(_809),_80b||{}); +var _80e={}; +b.map(function(n){ +var m=n.id.match(_80b.format); +if(m){ +_80e[m[1]]=[n,n.parentNode]; +} +n.parentNode.removeChild(n); +},self.findElements(_809,_80b)); +b.map(function(_811){ +var n=_80e[_811]; +if(n){ +n[1].appendChild(n[0]); +delete _80e[_811]; +} +},_80a); +},_constructIndex:function(node){ +var _814=""; +do{ +if(node.id){ +_814="["+node.position+"]"+_814; +} +}while((node=node.parent)!=null); +return _814; +},sequence:function(_815,_816){ +_815=MochiKit.DOM.getElement(_815); +var self=MochiKit.Sortable; +var _816=MochiKit.Base.update(self.options(_815),_816||{}); +return MochiKit.Base.map(function(item){ +return item.id.match(_816.format)?item.id.match(_816.format)[1]:""; +},MochiKit.DOM.getElement(self.findElements(_815,_816)||[])); +},serialize:function(_819,_81a){ +_819=MochiKit.DOM.getElement(_819); +var self=MochiKit.Sortable; +_81a=MochiKit.Base.update(self.options(_819),_81a||{}); +var name=encodeURIComponent(_81a.name||_819.id); +if(_81a.tree){ +return MochiKit.Base.flattenArray(MochiKit.Base.map(function(item){ +return [name+self._constructIndex(item)+"[id]="+encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); +},self.tree(_819,_81a).children)).join("&"); +}else{ +return MochiKit.Base.map(function(item){ +return name+"[]="+encodeURIComponent(item); +},self.sequence(_819,_81a)).join("&"); +} +}}); +MochiKit.Sortable.Sortable=MochiKit.Sortable; +MochiKit.Sortable.__new__=function(){ +MochiKit.Base.nameFunctions(this); +this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)}; +}; +MochiKit.Sortable.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.Sortable); +if(typeof (MochiKit)=="undefined"){ +MochiKit={}; +} +if(typeof (MochiKit.MochiKit)=="undefined"){ +MochiKit.MochiKit={}; +} +MochiKit.MochiKit.NAME="MochiKit.MochiKit"; +MochiKit.MochiKit.VERSION="1.4.2"; +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","Selector","Style","LoggingPane","Color","Signal","Position","Visual","DragAndDrop","Sortable"]; +if(typeof (JSAN)!="undefined"||typeof (dojo)!="undefined"){ +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.MochiKit"); +(function(lst){ +for(var i=0;i"); +} +} +})(); +} + + diff --git a/paste/evalexception/media/debug.js b/paste/evalexception/media/debug.js new file mode 100644 index 0000000..57f9df3 --- /dev/null +++ b/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 += '
\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/paste/evalexception/media/minus.jpg b/paste/evalexception/media/minus.jpg new file mode 100644 index 0000000..05f3306 Binary files /dev/null and b/paste/evalexception/media/minus.jpg differ diff --git a/paste/evalexception/media/plus.jpg b/paste/evalexception/media/plus.jpg new file mode 100644 index 0000000..a17aa5e Binary files /dev/null and b/paste/evalexception/media/plus.jpg differ diff --git a/paste/evalexception/middleware.py b/paste/evalexception/middleware.py new file mode 100644 index 0000000..da7876d --- /dev/null +++ b/paste/evalexception/middleware.py @@ -0,0 +1,618 @@ +# (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. +""" + +from __future__ import print_function + +import sys +import os +import cgi +import traceback +import six +from six.moves import cStringIO as 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 +from paste.evalexception 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 ``
`` and multiple spaces to use `` ``). + + If ``quote`` is true, then the value will be HTML quoted first. + """ + if quote: + v = html_quote(v) + v = v.replace('\n', '
\n') + v = re.sub(r'()( +)', _repl_nbsp, v) + v = re.sub(r'(\n)( +)', _repl_nbsp, v) + v = re.sub(r'^()( +)', _repl_nbsp, v) + return '%s' % v + +def _repl_nbsp(match): + if len(match.group(2)) == 1: + return ' ' + return match.group(1) + ' ' * (len(match.group(2))-1) + ' ' + +def simplecatcher(application): + """ + A simple middleware that catches errors and turns them into simple + tracebacks. + """ + def simplecatcher_app(environ, start_response): + try: + return application(environ, start_response) + except: + out = StringIO() + traceback.print_exc(file=out) + start_response('500 Server Error', + [('content-type', 'text/html')], + sys.exc_info()) + res = out.getvalue() + return ['

Error

%s
' + % 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 as e: + form['headers']['status'] = '500 Server Error' + return 'There was an error: %s' % 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 = six.next(debug_counter) + 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 ('>>> ' + '%s
\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) + msg = 'Debug at: %s\n' % view_uri + if six.PY3: + msg = msg.encode('utf8') + environ['wsgi.errors'].write(msg) + + exc_data = collector.collect_exception(*exc_info) + debug_info = DebugInfo(count, exc_info, exc_data, base_path, + environ, view_uri) + assert count not in self.debug_infos + self.debug_infos[count] = debug_info + + if self.xmlhttp_key: + get_vars = request.parse_querystring(environ) + if dict(get_vars).get(self.xmlhttp_key): + exc_data = collector.collect_exception(*exc_info) + html = formatter.format_html( + 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 = request.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} + if six.PY3: + page = page.encode('utf8') + return [page] + + def eval_javascript(self): + base_path = self.base_path + '/_debug' + return ( + '\n' + '\n' + '\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 + + '     ' + '    ' + % (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 as e: + print('Error: %s' % e, file=out) + 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 += '...' + value += '%s' % orig_value[100:] + value = formatter.make_wrappable(value) + if i % 2: + attr = ' class="even"' + else: + attr = ' class="odd"' + rows.append('' + '%s%s' + % (attr, html_quote(name), + preserve_whitespace(value, quote=False))) + return '%s
' % ( + '\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 = """ +
+ +
+ %s +
+ """ % long_er + else: + full_traceback_html = '' + + return """ + %s + %s +
+ +
+ +
+ """ % (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 ('
' % 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( + '' + % (html_quote(name), html_quote(value))) + return ''' +
+%s + +
''' % (url, '\n'.join(fields)) +""" + + +def input_form(tbid, debug_info): + return ''' +
+
+
+ + +
+ ''' % {'tbid': tbid} + +error_template = ''' + + + Server Error + %(head_html)s + + + + + +%(repost_button)s + +%(body)s + + + +''' + +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/paste/exceptions/__init__.py b/paste/exceptions/__init__.py new file mode 100644 index 0000000..813f855 --- /dev/null +++ b/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/paste/exceptions/collector.py b/paste/exceptions/collector.py new file mode 100644 index 0000000..632ce06 --- /dev/null +++ b/paste/exceptions/collector.py @@ -0,0 +1,523 @@ +# (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 +from six.moves import cStringIO as 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, 'keys'): + # 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 '__traceback_supplement__' in locals: + # Use the supplement defined in the function. + tbs = locals['__traceback_supplement__'] + elif '__traceback_supplement__' in globals: + # 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/paste/exceptions/errormiddleware.py b/paste/exceptions/errormiddleware.py new file mode 100644 index 0000000..95c1261 --- /dev/null +++ b/paste/exceptions/errormiddleware.py @@ -0,0 +1,466 @@ +# (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 +from six.moves import cStringIO as StringIO +from paste.exceptions import formatter, collector, reporter +from paste import wsgilib +from paste import request +import six + +__all__ = ['ErrorMiddleware', 'handle_exception'] + +class _NoDefault(object): + def __repr__(self): + return '' +NoDefault = _NoDefault() + +class ErrorMiddleware(object): + + """ + Error handling middleware + + Usage:: + + error_catching_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) + if six.PY3: + response = response.encode('utf8') + 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 = request.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 += ( + '
Error in .close():
%s' + % close_response) + + if not self.start_checker.response_started: + self.start_checker('500 Internal Server Error', + [('content-type', 'text/html')], + exc_info) + + if six.PY3: + response = response.encode('utf8') + return response + __next__ = next + + 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: + line = ('Error - %s: %s\n' + % (exc_data.exception_type, exc_data.exception_value)) + if six.PY3: + line = line.encode('utf8') + error_stream.write(line) + if html: + if debug_mode and simple_html_error: + return_error = formatter.format_html( + 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 """ +

Additionally an error occurred while sending the %s report: + +

%s
+

""" % ( + 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 ''' + + + Server Error + %s + + +

Server Error

+ %s + %s + + ''' % (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/paste/exceptions/formatter.py b/paste/exceptions/formatter.py new file mode 100644 index 0000000..09309de --- /dev/null +++ b/paste/exceptions/formatter.py @@ -0,0 +1,565 @@ +# (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 six +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, six.string_types): + 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 = sorted(items) + for n, v in items: + try: + v = repr(v) + except Exception as 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 '
%s
' % self.quote(s) + def emphasize(self, s): + return '%s' % s + def format_sup_url(self, url): + return 'URL: %s' % (url, url) + def format_combine_lines(self, lines): + return '
\n'.join(lines) + def format_source_line(self, filename, frame): + name = self.quote(frame.name or '?') + return 'Module %s:%s in %s' % ( + filename, frame.modname or '?', frame.lineno or '?', + name) + return 'File %r, line %s in %s' % ( + 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 ('' + '>>  %s' + % (q_long_source, + q_source)) + def format_source(self, source_line): + return '  %s' % self.quote(source_line.strip()) + def format_traceback_info(self, info): + return '
%s
' % 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:
%s
' % (title, self.quote(s)) + else: + return '%s: %s' % (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: [
\n    %s]
' % ( + title, ',
    '.join(map(self.quote, map(repr, value)))) + else: + return '%s: %s' % (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( + '\n' + + '
\n') + lines.extend([value for n, value in data_by_importance['extra']]) + lines.append('
') + 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 = sorted(rows) + table = ['' % table_class, + '' + % self.quote(title)] + odd = False + for name, value in rows: + try: + value = repr(value) + except Exception as e: + value = 'Cannot print: %s' % e + odd = not odd + table.append( + '' + % (odd and 'odd' or 'even', self.quote(name))) + table.append( + '' + % make_wrappable(self.quote(truncate(value)))) + table.append('
%s
%s%s
') + return '\n'.join(table) + +hide_display_js = r''' +''' + + +error_css = """ + +""" + +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 +
+ +
+ %s +
+
+ +
+ +
+ """ % (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'') +error_re = re.compile(r'

ERROR: .*?

') + +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] = '%s' % lines[i] + src = '
\n'.join(lines) + src = whitespace_re.sub( + lambda m: ' '*(len(m.group(0))-1) + ' ', src) + return src + +def truncate(string, limit=1000): + """ + Truncate the string to the limit number of + characters + """ + if len(string) > limit: + return string[:limit-20]+'...'+string[-17:] + else: + return string + +def make_wrappable(html, wrap_limit=60, + split_on=';?&@!$#-/\\"\''): + # Currently using , maybe should use ​ + # http://www.cs.tut.fi/~jkorpela/html/nobr.html + if len(html) <= wrap_limit: + return html + words = html.split() + new_words = [] + for word in words: + wrapped_word = '' + while len(word) > wrap_limit: + for char in split_on: + if char in word: + first, rest = word.split(char, 1) + wrapped_word += first+char+'' + word = rest + break + else: + for i in range(0, len(word), wrap_limit): + wrapped_word += word[i:i+wrap_limit]+'' + 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 ``
`` 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 = ''.join(parts)
+                    break
+        new_lines.append(line)
+    return '\n'.join(lines)
diff --git a/paste/exceptions/reporter.py b/paste/exceptions/reporter.py
new file mode 100644
index 0000000..7c0c266
--- /dev/null
+++ b/paste/exceptions/reporter.py
@@ -0,0 +1,141 @@
+# (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.mime.text import MIMEText
+from email.mime.multipart 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)
+        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/paste/exceptions/serial_number_generator.py b/paste/exceptions/serial_number_generator.py
new file mode 100644
index 0000000..3f80107
--- /dev/null
+++ b/paste/exceptions/serial_number_generator.py
@@ -0,0 +1,129 @@
+# (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
+
+import six
+
+good_characters = "23456789abcdefghjkmnpqrtuvwxyz"
+
+base = len(good_characters)
+
+def make_identifier(number):
+    """
+    Encodes a number as an identifier.
+    """
+    if not isinstance(number, six.integer_types):
+        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, six.text_type):
+        s = s.encode('utf-8')
+    elif not isinstance(s, six.binary_type):
+        s = str(s)
+        if six.PY3:
+            s = s.encode('utf-8')
+    h = hasher(s)
+    bin_hash = h.digest()
+    modulo = base ** length
+    number = 0
+    for c in list(bin_hash):
+        number = (number * 256 + six.byte2int([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/paste/fileapp.py b/paste/fileapp.py
new file mode 100644
index 0000000..e18281a
--- /dev/null
+++ b/paste/fileapp.py
@@ -0,0 +1,356 @@
+# (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 '"%s-%s"' % (self.last_modified, 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 [b'']
+        except HTTPBadRequest as 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 is not None
+                    and client_clock >= int(self.last_modified)):
+                    # horribly inefficient, n^2 performance, yuck!
+                    for head in list_headers(entity=True):
+                        head.delete(headers)
+                    start_response('304 Not Modified', headers)
+                    return [b''] # empty body
+            except HTTPBadRequest as 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 range or content_length != self.content_length:
+            start_response('206 Partial Content', headers)
+        else:
+            start_response('200 OK', 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) as 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 [b'']
+            return retval
+        (lower, content_length) = retval
+        if is_head:
+            return [b'']
+        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
+    __next__ = next
+
+    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)
+        # 'None' is not a valid content-encoding, so don't set the header if
+        # mimetypes.guess_type returns None
+        if content_encoding is not None:
+            app = DataApp(None, content_type = content_type,
+                                content_encoding = content_encoding)
+        else:
+            app = DataApp(None, content_type = content_type)
+        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/paste/fixture.py b/paste/fixture.py
new file mode 100644
index 0000000..363f119
--- /dev/null
+++ b/paste/fixture.py
@@ -0,0 +1,1755 @@
+# (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 `_
+for testing WSGI applications, and the `TestFileEnvironment
+`_ class for testing the
+effects of command-line scripts.
+"""
+
+from __future__ import print_function
+
+import sys
+import random
+import mimetypes
+import time
+import os
+import shutil
+import smtplib
+import shlex
+import re
+import six
+import subprocess
+from six.moves import cStringIO as StringIO
+from six.moves.urllib.parse import urlencode
+from six.moves.urllib import parse as urlparse
+try:
+    # Python 3
+    from http.cookies import BaseCookie
+    from urllib.parse import splittype, splithost
+except ImportError:
+    # Python 2
+    from Cookie import BaseCookie
+    from urllib import splittype, splithost
+
+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, (six.binary_type, six.text_type)):
+            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
+        `_
+        """
+        if extra_environ is None:
+            extra_environ = {}
+        # Hide from py.test:
+        __tracebackhide__ = True
+        if params:
+            if not isinstance(params, (six.binary_type, six.text_type)):
+                params = 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=b'', 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 = urlencode(params)
+        if hasattr(params, 'items'):
+            # Some other multi-dict like format
+            params = urlencode(params.items())
+        if six.PY3 and isinstance(params, six.text_type):
+            params = params.encode('utf8')
+        if upload_files:
+            params = urlparse.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'] = six.BytesIO(params)
+        self._set_headers(headers, environ)
+        environ.update(extra_environ)
+        req = TestRequest(url, environ, expect_errors)
+        return self.do_request(req, status=status)
+
+    def post(self, url, params=b'', 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
+        `_
+        """
+        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=b'', 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
+        `_
+        """
+        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=b'', 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
+        `_
+        """
+        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()
+        content_type = 'multipart/form-data; boundary=%s' % boundary
+        if six.PY3:
+            boundary = boundary.encode('ascii')
+
+        lines = []
+        for key, value in params:
+            lines.append(b'--'+boundary)
+            line = 'Content-Disposition: form-data; name="%s"' % key
+            if six.PY3:
+                line = line.encode('utf8')
+            lines.append(line)
+            lines.append(b'')
+            line = value
+            if six.PY3 and isinstance(line, six.text_type):
+                line = line.encode('utf8')
+            lines.append(line)
+        for file_info in files:
+            key, filename, value = self._get_file_info(file_info)
+            lines.append(b'--'+boundary)
+            line = ('Content-Disposition: form-data; name="%s"; filename="%s"'
+                         % (key, filename))
+            if six.PY3:
+                line = line.encode('utf8')
+            lines.append(line)
+            fcontent = mimetypes.guess_type(filename)[0]
+            line = ('Content-Type: %s'
+                    % (fcontent or 'application/octet-stream'))
+            if six.PY3:
+                line = line.encode('utf8')
+            lines.append(line)
+            lines.append(b'')
+            lines.append(value)
+        lines.append(b'--' + boundary + b'--')
+        lines.append(b'')
+        body = b'\r\n'.join(lines)
+        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
+            body = res.body
+            if six.PY3:
+                body = body.decode('utf8', 'xmlcharrefreplace')
+            raise AppError(
+                "Bad response: %s (not 200 OK or 3xx redirect for %s)\n%s"
+                % (res.full_status, res.request.url,
+                   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, resp, total_time):
+        status, headers, body, errors = resp
+        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
+    `_
+    """
+
+    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 
s found on the page (instances of + `Form `_) + """) + + 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 + `_ 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, ( + " 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 = splittype(location) + host, path = 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 ```` and + ````) + + * ``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 + ``