Replace pecan's homegrown interactive debugging middleware with backlash
backlash is a port of Werkzeug's debugger middleware to Webob. It has no additional dependencies beyond Webob and is being used by the TurboGears2 team as an alternative to the antiquated Paste/WebError. Leveraging this as an *optional* dependency to pecan would: * Remove a sizable chunk of code from pecan, some of which is embedded JavaScript that packagers have traditionally balked at. * Improve the interactive debugging experience for developers in a very meaningful way (the Werkzeug-based middleware provides features like an in-browser console debugger, the ability to load source code on a frame-by-frame basis). * Improve the unified debugging experience amongst several popular Python frameworks (some form of the debugging interface will be in use by Flask, Pecan, and TurboGears2). Change-Id: I85f50f677c6052bd2afd32811dedf33835135e12
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 54 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 6.6 KiB  | 
@@ -21,37 +21,18 @@ in your applications. To enable the debugging middleware, simply set the
 | 
			
		||||
 | 
			
		||||
Once enabled, the middleware will automatically catch exceptions raised by your
 | 
			
		||||
application and display the Python stack trace and WSGI environment in your 
 | 
			
		||||
browser for easy debugging:
 | 
			
		||||
browser when runtime exceptions are raised.
 | 
			
		||||
 | 
			
		||||
.. figure:: debug-middleware-1.png
 | 
			
		||||
   :alt: Pecan debug middleware sample output.
 | 
			
		||||
   :width: 90%
 | 
			
		||||
To improve debugging, including support for an interactive browser-based
 | 
			
		||||
console, Pecan makes use of the Python `backlash
 | 
			
		||||
<https://pypi.python.org/pypi/backlash>` library.  You’ll need to install it
 | 
			
		||||
for development use before continuing::
 | 
			
		||||
 | 
			
		||||
To further aid in debugging, the middleware includes the ability to repeat the
 | 
			
		||||
offending request, automatically inserting a breakpoint, and dropping your
 | 
			
		||||
console into the Python debugger, ``pdb.post_mortem``:
 | 
			
		||||
    $ pip install backlash
 | 
			
		||||
    Downloading/unpacking backlash
 | 
			
		||||
    ...
 | 
			
		||||
    Successfully installed backlash
 | 
			
		||||
 | 
			
		||||
.. figure:: debug-middleware-2.png
 | 
			
		||||
   :alt: Pecan debug middleware request debugger. 
 | 
			
		||||
 | 
			
		||||
You can also use any debugger with a suitable ``post_mortem`` entry point.
 | 
			
		||||
For example, to use the `PuDB Debugger <http://pypi.python.org/pypi/pudb>`_,
 | 
			
		||||
set ``debugger`` like so::
 | 
			
		||||
 | 
			
		||||
    import pudb
 | 
			
		||||
 | 
			
		||||
    app = {
 | 
			
		||||
        ...
 | 
			
		||||
        'debug': True,
 | 
			
		||||
        'debugger': pudb.post_mortem,
 | 
			
		||||
        ...
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
.. seealso::
 | 
			
		||||
 | 
			
		||||
  Refer to the `pdb documentation
 | 
			
		||||
  <http://docs.python.org/library/pdb.html>`_ for more information on
 | 
			
		||||
  using the Python debugger.
 | 
			
		||||
 | 
			
		||||
Serving Static Files
 | 
			
		||||
--------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@ try:
 | 
			
		||||
except ImportError:
 | 
			
		||||
    from logutils.dictconfig import dictConfig as load_logging_config  # noqa
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
import warnings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -44,8 +43,6 @@ def make_app(root, **kw):
 | 
			
		||||
                        debug mode is set.
 | 
			
		||||
    :param debug: A flag to enable debug mode.  This enables the debug
 | 
			
		||||
                  middleware and serving static files.
 | 
			
		||||
    :param debugger: A callable to start debugging, defaulting to the Python
 | 
			
		||||
                     debugger entry point ``pdb.post_mortem``.
 | 
			
		||||
    :param wrap_app: A function or middleware class to wrap the Pecan app.
 | 
			
		||||
                     This must either be a wsgi middleware class or a
 | 
			
		||||
                     function that returns a wsgi application. This wrapper
 | 
			
		||||
@@ -101,19 +98,19 @@ def make_app(root, **kw):
 | 
			
		||||
    # Included for internal redirect support
 | 
			
		||||
    app = middleware.recursive.RecursiveMiddleware(app)
 | 
			
		||||
 | 
			
		||||
    # When in debug mode, load our exception dumping middleware
 | 
			
		||||
    # When in debug mode, load exception debugging middleware
 | 
			
		||||
    static_root = kw.get('static_root', None)
 | 
			
		||||
    if debug:
 | 
			
		||||
        debugger = kw.get('debugger', None)
 | 
			
		||||
        debugger_kwargs = {}
 | 
			
		||||
        if six.callable(debugger):
 | 
			
		||||
            debugger_kwargs['debugger'] = debugger
 | 
			
		||||
        elif debugger:
 | 
			
		||||
            warnings.warn(
 | 
			
		||||
                "`app.debugger` is not callable, ignoring",
 | 
			
		||||
                RuntimeWarning
 | 
			
		||||
            )
 | 
			
		||||
        app = middleware.debug.DebugMiddleware(app, **debugger_kwargs)
 | 
			
		||||
        debug_kwargs = getattr(conf, 'debug', {})
 | 
			
		||||
        debug_kwargs.setdefault('context_injectors', []).append(
 | 
			
		||||
            lambda environ: {
 | 
			
		||||
                'request': environ.get('pecan.locals', {}).get('request')
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        app = DebugMiddleware(
 | 
			
		||||
            app,
 | 
			
		||||
            **debug_kwargs
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Support for serving static files (for development convenience)
 | 
			
		||||
        if static_root:
 | 
			
		||||
 
 | 
			
		||||
@@ -660,6 +660,10 @@ class PecanBase(object):
 | 
			
		||||
        req = self.request_cls(environ)
 | 
			
		||||
        resp = self.response_cls()
 | 
			
		||||
        state = RoutingState(req, resp, self)
 | 
			
		||||
        environ['pecan.locals'] = {
 | 
			
		||||
            'request': req,
 | 
			
		||||
            'response': resp
 | 
			
		||||
        }
 | 
			
		||||
        controller = None
 | 
			
		||||
 | 
			
		||||
        # handle the request
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
from . import debug
 | 
			
		||||
from . import errordocument
 | 
			
		||||
from . import recursive
 | 
			
		||||
from . import static
 | 
			
		||||
 
 | 
			
		||||
@@ -1,324 +1,96 @@
 | 
			
		||||
from traceback import print_exc
 | 
			
		||||
from pprint import pformat
 | 
			
		||||
import pdb
 | 
			
		||||
__CONFIG_HELP__ = '''
 | 
			
		||||
<div class="traceback">
 | 
			
		||||
  <b>To disable this interface, set </b>
 | 
			
		||||
  <a target="window"
 | 
			
		||||
  href="https://pecan.readthedocs.org/en/latest/deployment.html#disabling-debug-mode">
 | 
			
		||||
    <pre>conf.app.debug = False</pre>
 | 
			
		||||
  </a>
 | 
			
		||||
</div>
 | 
			
		||||
'''  # noqa
 | 
			
		||||
 | 
			
		||||
from six.moves import cStringIO as StringIO
 | 
			
		||||
try:
 | 
			
		||||
    import re
 | 
			
		||||
    from backlash.debug import DebuggedApplication
 | 
			
		||||
 | 
			
		||||
from mako.template import Template
 | 
			
		||||
from webob import Response
 | 
			
		||||
    class DebugMiddleware(DebuggedApplication):
 | 
			
		||||
 | 
			
		||||
from .resources import (pecan_image, xregexp_js, syntax_js, syntax_css, theme,
 | 
			
		||||
                        brush)
 | 
			
		||||
        body_re = re.compile('(<body[^>]*>)', re.I)
 | 
			
		||||
 | 
			
		||||
        def debug_application(self, environ, start_response):
 | 
			
		||||
            for part in super(DebugMiddleware, self).debug_application(
 | 
			
		||||
                environ, start_response
 | 
			
		||||
            ):
 | 
			
		||||
                yield self.body_re.sub('\g<1>%s' % __CONFIG_HELP__, part)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
debug_template_raw = '''<html>
 | 
			
		||||
 <head>
 | 
			
		||||
  <title>Pecan - Application Error</title>
 | 
			
		||||
except ImportError:
 | 
			
		||||
    from traceback import print_exc
 | 
			
		||||
    from pprint import pformat
 | 
			
		||||
 | 
			
		||||
  <link rel="stylesheet" type="text/css" href="${syntax_css}" />
 | 
			
		||||
  <link rel="stylesheet" type="text/css" href="${theme}" />
 | 
			
		||||
    from mako.template import Template
 | 
			
		||||
    from six.moves import cStringIO as StringIO
 | 
			
		||||
    from webob import Response
 | 
			
		||||
    from webob.exc import HTTPException
 | 
			
		||||
 | 
			
		||||
  <script type="text/javascript" src="${xregexp_js}"></script>
 | 
			
		||||
  <script type="text/javascript" src="${syntax_js}">
 | 
			
		||||
    /**
 | 
			
		||||
     * SyntaxHighlighter
 | 
			
		||||
     * http://alexgorbatchev.com/SyntaxHighlighter
 | 
			
		||||
     *
 | 
			
		||||
     * SyntaxHighlighter is donationware. If you are using it, please donate.
 | 
			
		||||
     * http://alexgorbatchev.com/SyntaxHighlighter/donate.html
 | 
			
		||||
     *
 | 
			
		||||
     * @version
 | 
			
		||||
     * 3.0.83 (July 02 2010)
 | 
			
		||||
     *
 | 
			
		||||
     * @copyright
 | 
			
		||||
     * Copyright (C) 2004-2010 Alex Gorbatchev.
 | 
			
		||||
     *
 | 
			
		||||
     * @license
 | 
			
		||||
     * Dual licensed under the MIT and GPL licenses.
 | 
			
		||||
     */
 | 
			
		||||
  </script>
 | 
			
		||||
  <script type="text/javascript" src="${brush}">
 | 
			
		||||
    /**
 | 
			
		||||
     * SyntaxHighlighter
 | 
			
		||||
     * http://alexgorbatchev.com/SyntaxHighlighter
 | 
			
		||||
     *
 | 
			
		||||
     * SyntaxHighlighter is donationware. If you are using it, please donate.
 | 
			
		||||
     * http://alexgorbatchev.com/SyntaxHighlighter/donate.html
 | 
			
		||||
     *
 | 
			
		||||
     * @version
 | 
			
		||||
     * 3.0.83 (July 02 2010)
 | 
			
		||||
     *
 | 
			
		||||
     * @copyright
 | 
			
		||||
     * Copyright (C) 2004-2010 Alex Gorbatchev.
 | 
			
		||||
     *
 | 
			
		||||
     * @license
 | 
			
		||||
     * Dual licensed under the MIT and GPL licenses.
 | 
			
		||||
     */
 | 
			
		||||
  </script>
 | 
			
		||||
    debug_template_raw = '''<html>
 | 
			
		||||
     <head>
 | 
			
		||||
      <title>Pecan - Application Error</title>
 | 
			
		||||
     <body>
 | 
			
		||||
      <header>
 | 
			
		||||
        <h1>
 | 
			
		||||
          An error occurred!
 | 
			
		||||
        </h1>
 | 
			
		||||
      </header>
 | 
			
		||||
      <div id="error-content">
 | 
			
		||||
        <p>
 | 
			
		||||
          %(config_help)s
 | 
			
		||||
          Pecan offers support for interactive debugging by installing the <a href="https://pypi.python.org/pypi/backlash" target="window">backlash</a> package:
 | 
			
		||||
          <br />
 | 
			
		||||
          <b><pre>pip install backlash</pre></b>
 | 
			
		||||
          ...and reloading this page.
 | 
			
		||||
        </p>
 | 
			
		||||
        <h2>Traceback</h2>
 | 
			
		||||
        <div id="traceback">
 | 
			
		||||
          <pre>${traceback}</pre>
 | 
			
		||||
        </div>
 | 
			
		||||
        <h2>WSGI Environment</h2>
 | 
			
		||||
        <div id="environ">
 | 
			
		||||
          <pre>${environment}</pre>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
     </body>
 | 
			
		||||
    </html>
 | 
			
		||||
    ''' % {'config_help': __CONFIG_HELP__}  # noqa
 | 
			
		||||
 | 
			
		||||
  <style type="text/css">
 | 
			
		||||
    body {
 | 
			
		||||
      color: #000;
 | 
			
		||||
      background: #FFF;
 | 
			
		||||
      font-family: 'Helvetica Neue', 'Helvetica', 'Verdana', sans-serif;
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      padding: 0;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
    }
 | 
			
		||||
    debug_template = Template(debug_template_raw)
 | 
			
		||||
 | 
			
		||||
    a {
 | 
			
		||||
      color: #FAFF78;
 | 
			
		||||
    }
 | 
			
		||||
    class DebugMiddleware(object):
 | 
			
		||||
 | 
			
		||||
    h1, h2, h3, h4, h5, h6 {
 | 
			
		||||
      font-family: 'Helvetica', sans-serif;
 | 
			
		||||
    }
 | 
			
		||||
        def __init__(self, app, *args, **kwargs):
 | 
			
		||||
            self.app = app
 | 
			
		||||
 | 
			
		||||
    h1 {
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      padding: .75em 1.5em 1em 1.5em;
 | 
			
		||||
      color: #F90;
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      font-weight: bold;
 | 
			
		||||
    }
 | 
			
		||||
        def __call__(self, environ, start_response):
 | 
			
		||||
            try:
 | 
			
		||||
                return self.app(environ, start_response)
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
                # get a formatted exception
 | 
			
		||||
                out = StringIO()
 | 
			
		||||
                print_exc(file=out)
 | 
			
		||||
 | 
			
		||||
    h1 img  {
 | 
			
		||||
      padding-right: 5px;
 | 
			
		||||
    }
 | 
			
		||||
                # get formatted WSGI environment
 | 
			
		||||
                formatted_environ = pformat(environ)
 | 
			
		||||
 | 
			
		||||
    h2 {
 | 
			
		||||
      color: #311F00;
 | 
			
		||||
    }
 | 
			
		||||
                # render our template
 | 
			
		||||
                result = debug_template.render(
 | 
			
		||||
                    traceback=out.getvalue(),
 | 
			
		||||
                    environment=formatted_environ
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    header  {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      background: #311F00;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    div#error-content  {
 | 
			
		||||
      padding: 0 2em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .syntaxhighlighter a,
 | 
			
		||||
    .syntaxhighlighter div,
 | 
			
		||||
    .syntaxhighlighter code,
 | 
			
		||||
    .syntaxhighlighter table,
 | 
			
		||||
    .syntaxhighlighter table td,
 | 
			
		||||
    .syntaxhighlighter table tr,
 | 
			
		||||
    .syntaxhighlighter table tbody,
 | 
			
		||||
    .syntaxhighlighter table thead,
 | 
			
		||||
    .syntaxhighlighter table caption,
 | 
			
		||||
    .syntaxhighlighter textarea {
 | 
			
		||||
      font-family: monospace !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .syntaxhighlighter .container {
 | 
			
		||||
       background: #FDF6E3 !important;
 | 
			
		||||
       padding: 1em !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .syntaxhighlighter .container .line {
 | 
			
		||||
       background: #FDF6E3 !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .syntaxhighlighter .container .line .python.string {
 | 
			
		||||
       color: #C70 !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #debug {
 | 
			
		||||
        background: #FDF6E3;
 | 
			
		||||
        padding: 10px !important;
 | 
			
		||||
        margin-top: 10px;
 | 
			
		||||
        font-family: monospace;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  </style>
 | 
			
		||||
  <script type="text/javascript">
 | 
			
		||||
      SyntaxHighlighter.defaults['gutter'] = false;
 | 
			
		||||
      SyntaxHighlighter.defaults['toolbar'] = false;
 | 
			
		||||
      SyntaxHighlighter.all()
 | 
			
		||||
  </script>
 | 
			
		||||
 | 
			
		||||
  <script type="text/javascript">
 | 
			
		||||
    function get_request() {
 | 
			
		||||
        /* ajax sans jquery makes me sad */
 | 
			
		||||
        var request = false;
 | 
			
		||||
 | 
			
		||||
        // Mozilla/Safari
 | 
			
		||||
        if (window.XMLHttpRequest) {
 | 
			
		||||
            request = new XMLHttpRequest();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // IE
 | 
			
		||||
        else if (window.ActiveXObject) {
 | 
			
		||||
            request = new ActiveXObject("Microsoft.XMLHTTP");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return request;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function debug_request(btn) {
 | 
			
		||||
        btn.disabled = true;
 | 
			
		||||
 | 
			
		||||
        request = get_request();
 | 
			
		||||
        request.open('GET', '/__pecan_initiate_pdb__', true);
 | 
			
		||||
        request.onreadystatechange = function() {
 | 
			
		||||
            if (request.readyState == 4) {
 | 
			
		||||
                btn.disabled = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        request.send('');
 | 
			
		||||
 | 
			
		||||
        /* automatically timeout after 5 minutes, re-enabling the button */
 | 
			
		||||
        setTimeout(function() {
 | 
			
		||||
           request.abort();
 | 
			
		||||
        }, 5 * 60 * 1000);
 | 
			
		||||
    }
 | 
			
		||||
  </script>
 | 
			
		||||
 </head>
 | 
			
		||||
 <body>
 | 
			
		||||
 | 
			
		||||
  <header>
 | 
			
		||||
    <h1>
 | 
			
		||||
      <img style="padding-top: 7px"
 | 
			
		||||
           align="center" alt="pecan logo"
 | 
			
		||||
           height="25"
 | 
			
		||||
           src="${pecan_image}" />
 | 
			
		||||
      application error
 | 
			
		||||
    </h1>
 | 
			
		||||
  </header>
 | 
			
		||||
 | 
			
		||||
  <div id="error-content">
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      <b>To disable this interface, set </b>
 | 
			
		||||
      <pre class="brush: python">conf.app.debug = False</pre>
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <h2>Traceback</h2>
 | 
			
		||||
    <div id="traceback">
 | 
			
		||||
      <pre class="brush: python">${traceback}</pre>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    % if not debugging:
 | 
			
		||||
    <b>Want to debug this request?</b>
 | 
			
		||||
    <div id="debug">
 | 
			
		||||
      You can <button onclick="debug_request(this)">
 | 
			
		||||
        repeat this request
 | 
			
		||||
      </button> with a Python debugger breakpoint.
 | 
			
		||||
    </div>
 | 
			
		||||
    % endif
 | 
			
		||||
 | 
			
		||||
    <h2>WSGI Environment</h2>
 | 
			
		||||
    <div id="environ">
 | 
			
		||||
      <pre class="brush: python">${environment}</pre>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 </body>
 | 
			
		||||
</html>
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
debug_template = Template(debug_template_raw)
 | 
			
		||||
__debug_environ__ = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PdbMiddleware(object):
 | 
			
		||||
    def __init__(self, app, debugger):
 | 
			
		||||
        self.app = app
 | 
			
		||||
        self.debugger = debugger
 | 
			
		||||
 | 
			
		||||
    def __call__(self, environ, start_response):
 | 
			
		||||
        try:
 | 
			
		||||
            return self.app(environ, start_response)
 | 
			
		||||
        except:
 | 
			
		||||
            self.debugger()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DebugMiddleware(object):
 | 
			
		||||
    """A WSGI middleware that provides debugging assistance for development
 | 
			
		||||
    environments.
 | 
			
		||||
 | 
			
		||||
    To enable the debugging middleware, simply set the ``debug`` flag to
 | 
			
		||||
    ``True`` in your configuration file::
 | 
			
		||||
 | 
			
		||||
        app = {
 | 
			
		||||
            ...
 | 
			
		||||
            'debug': True,
 | 
			
		||||
            ...
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    Once enabled, the middleware will automatically catch exceptions raised by
 | 
			
		||||
    your application, and display the Python stack trace and WSGI environment
 | 
			
		||||
    in your browser for easy debugging.
 | 
			
		||||
 | 
			
		||||
    To further aid in debugging, the middleware includes the ability to repeat
 | 
			
		||||
    the offending request, automatically inserting a breakpoint, and dropping
 | 
			
		||||
    your console into the Python debugger, ``pdb.post_mortem``.
 | 
			
		||||
 | 
			
		||||
    You can also use any debugger with a suitable ``post_mortem`` entry point
 | 
			
		||||
    such as the `PuDB Debugger <http://pypi.python.org/pypi/pudb>`_,
 | 
			
		||||
 | 
			
		||||
    For more information, refer to the  `documentation for pdb
 | 
			
		||||
    <http://docs.python.org/library/pdb.html>`_ available on the Python
 | 
			
		||||
    website.
 | 
			
		||||
 | 
			
		||||
    :param app: the application to wrap.
 | 
			
		||||
    :param debugger: a callable to start debugging, defaulting to the Python
 | 
			
		||||
                     debugger entry point ``pdb.post_mortem``.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, app, debugger=pdb.post_mortem):
 | 
			
		||||
        self.app = app
 | 
			
		||||
        self.debugger = debugger
 | 
			
		||||
 | 
			
		||||
    def __call__(self, environ, start_response):
 | 
			
		||||
        if environ['wsgi.multiprocess']:
 | 
			
		||||
            raise RuntimeError(
 | 
			
		||||
                "The DebugMiddleware middleware is not usable in a "
 | 
			
		||||
                "multi-process environment"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if environ.get('paste.testing'):
 | 
			
		||||
            return self.app(environ, start_response)
 | 
			
		||||
 | 
			
		||||
        # initiate a PDB session if requested
 | 
			
		||||
        global __debug_environ__
 | 
			
		||||
        debugging = environ['PATH_INFO'] == '/__pecan_initiate_pdb__'
 | 
			
		||||
        if debugging:
 | 
			
		||||
            PdbMiddleware(self.app, self.debugger)(
 | 
			
		||||
                __debug_environ__, start_response
 | 
			
		||||
            )
 | 
			
		||||
            environ = __debug_environ__
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            return self.app(environ, start_response)
 | 
			
		||||
        except:
 | 
			
		||||
            # save the environ for debugging
 | 
			
		||||
            if not debugging:
 | 
			
		||||
                __debug_environ__ = environ
 | 
			
		||||
 | 
			
		||||
            # get a formatted exception
 | 
			
		||||
            out = StringIO()
 | 
			
		||||
            print_exc(file=out)
 | 
			
		||||
 | 
			
		||||
            # get formatted WSGI environment
 | 
			
		||||
            formatted_environ = pformat(environ)
 | 
			
		||||
 | 
			
		||||
            # render our template
 | 
			
		||||
            result = debug_template.render(
 | 
			
		||||
                traceback=out.getvalue(),
 | 
			
		||||
                environment=formatted_environ,
 | 
			
		||||
                pecan_image=pecan_image,
 | 
			
		||||
                xregexp_js=xregexp_js,
 | 
			
		||||
                syntax_js=syntax_js,
 | 
			
		||||
                brush=brush,
 | 
			
		||||
                syntax_css=syntax_css,
 | 
			
		||||
                theme=theme,
 | 
			
		||||
                debugging=debugging
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # construct and return our response
 | 
			
		||||
            response = Response()
 | 
			
		||||
            response.status_int = 400
 | 
			
		||||
            response.unicode_body = result
 | 
			
		||||
            return response(environ, start_response)
 | 
			
		||||
                # construct and return our response
 | 
			
		||||
                response = Response()
 | 
			
		||||
                if isinstance(exc, HTTPException):
 | 
			
		||||
                    response.status_int = exc.status
 | 
			
		||||
                else:
 | 
			
		||||
                    response.status_int = 500
 | 
			
		||||
                response.unicode_body = result
 | 
			
		||||
                return response(environ, start_response)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,664 +0,0 @@
 | 
			
		||||
// XRegExp 1.5.1
 | 
			
		||||
// (c) 2007-2012 Steven Levithan
 | 
			
		||||
// MIT License
 | 
			
		||||
// <http://xregexp.com>
 | 
			
		||||
// Provides an augmented, extensible, cross-browser implementation of regular expressions,
 | 
			
		||||
// including support for additional syntax, flags, and methods
 | 
			
		||||
 | 
			
		||||
var XRegExp;
 | 
			
		||||
 | 
			
		||||
if (XRegExp) {
 | 
			
		||||
    // Avoid running twice, since that would break references to native globals
 | 
			
		||||
    throw Error("can't load XRegExp twice in the same frame");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run within an anonymous function to protect variables and avoid new globals
 | 
			
		||||
(function (undefined) {
 | 
			
		||||
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
    //  Constructor
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
 | 
			
		||||
    // Accepts a pattern and flags; returns a new, extended `RegExp` object. Differs from a native
 | 
			
		||||
    // regular expression in that additional syntax and flags are supported and cross-browser
 | 
			
		||||
    // syntax inconsistencies are ameliorated. `XRegExp(/regex/)` clones an existing regex and
 | 
			
		||||
    // converts to type XRegExp
 | 
			
		||||
    XRegExp = function (pattern, flags) {
 | 
			
		||||
        var output = [],
 | 
			
		||||
            currScope = XRegExp.OUTSIDE_CLASS,
 | 
			
		||||
            pos = 0,
 | 
			
		||||
            context, tokenResult, match, chr, regex;
 | 
			
		||||
 | 
			
		||||
        if (XRegExp.isRegExp(pattern)) {
 | 
			
		||||
            if (flags !== undefined)
 | 
			
		||||
                throw TypeError("can't supply flags when constructing one RegExp from another");
 | 
			
		||||
            return clone(pattern);
 | 
			
		||||
        }
 | 
			
		||||
        // Tokens become part of the regex construction process, so protect against infinite
 | 
			
		||||
        // recursion when an XRegExp is constructed within a token handler or trigger
 | 
			
		||||
        if (isInsideConstructor)
 | 
			
		||||
            throw Error("can't call the XRegExp constructor within token definition functions");
 | 
			
		||||
 | 
			
		||||
        flags = flags || "";
 | 
			
		||||
        context = { // `this` object for custom tokens
 | 
			
		||||
            hasNamedCapture: false,
 | 
			
		||||
            captureNames: [],
 | 
			
		||||
            hasFlag: function (flag) {return flags.indexOf(flag) > -1;},
 | 
			
		||||
            setFlag: function (flag) {flags += flag;}
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        while (pos < pattern.length) {
 | 
			
		||||
            // Check for custom tokens at the current position
 | 
			
		||||
            tokenResult = runTokens(pattern, pos, currScope, context);
 | 
			
		||||
 | 
			
		||||
            if (tokenResult) {
 | 
			
		||||
                output.push(tokenResult.output);
 | 
			
		||||
                pos += (tokenResult.match[0].length || 1);
 | 
			
		||||
            } else {
 | 
			
		||||
                // Check for native multicharacter metasequences (excluding character classes) at
 | 
			
		||||
                // the current position
 | 
			
		||||
                if (match = nativ.exec.call(nativeTokens[currScope], pattern.slice(pos))) {
 | 
			
		||||
                    output.push(match[0]);
 | 
			
		||||
                    pos += match[0].length;
 | 
			
		||||
                } else {
 | 
			
		||||
                    chr = pattern.charAt(pos);
 | 
			
		||||
                    if (chr === "[")
 | 
			
		||||
                        currScope = XRegExp.INSIDE_CLASS;
 | 
			
		||||
                    else if (chr === "]")
 | 
			
		||||
                        currScope = XRegExp.OUTSIDE_CLASS;
 | 
			
		||||
                    // Advance position one character
 | 
			
		||||
                    output.push(chr);
 | 
			
		||||
                    pos++;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        regex = RegExp(output.join(""), nativ.replace.call(flags, flagClip, ""));
 | 
			
		||||
        regex._xregexp = {
 | 
			
		||||
            source: pattern,
 | 
			
		||||
            captureNames: context.hasNamedCapture ? context.captureNames : null
 | 
			
		||||
        };
 | 
			
		||||
        return regex;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
    //  Public properties
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
 | 
			
		||||
    XRegExp.version = "1.5.1";
 | 
			
		||||
 | 
			
		||||
    // Token scope bitflags
 | 
			
		||||
    XRegExp.INSIDE_CLASS = 1;
 | 
			
		||||
    XRegExp.OUTSIDE_CLASS = 2;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
    //  Private variables
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
 | 
			
		||||
    var replacementToken = /\$(?:(\d\d?|[$&`'])|{([$\w]+)})/g,
 | 
			
		||||
        flagClip = /[^gimy]+|([\s\S])(?=[\s\S]*\1)/g, // Nonnative and duplicate flags
 | 
			
		||||
        quantifier = /^(?:[?*+]|{\d+(?:,\d*)?})\??/,
 | 
			
		||||
        isInsideConstructor = false,
 | 
			
		||||
        tokens = [],
 | 
			
		||||
        // Copy native globals for reference ("native" is an ES3 reserved keyword)
 | 
			
		||||
        nativ = {
 | 
			
		||||
            exec: RegExp.prototype.exec,
 | 
			
		||||
            test: RegExp.prototype.test,
 | 
			
		||||
            match: String.prototype.match,
 | 
			
		||||
            replace: String.prototype.replace,
 | 
			
		||||
            split: String.prototype.split
 | 
			
		||||
        },
 | 
			
		||||
        compliantExecNpcg = nativ.exec.call(/()??/, "")[1] === undefined, // check `exec` handling of nonparticipating capturing groups
 | 
			
		||||
        compliantLastIndexIncrement = function () {
 | 
			
		||||
            var x = /^/g;
 | 
			
		||||
            nativ.test.call(x, "");
 | 
			
		||||
            return !x.lastIndex;
 | 
			
		||||
        }(),
 | 
			
		||||
        hasNativeY = RegExp.prototype.sticky !== undefined,
 | 
			
		||||
        nativeTokens = {};
 | 
			
		||||
 | 
			
		||||
    // `nativeTokens` match native multicharacter metasequences only (including deprecated octals,
 | 
			
		||||
    // excluding character classes)
 | 
			
		||||
    nativeTokens[XRegExp.INSIDE_CLASS] = /^(?:\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S]))/;
 | 
			
		||||
    nativeTokens[XRegExp.OUTSIDE_CLASS] = /^(?:\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S])|\(\?[:=!]|[?*+]\?|{\d+(?:,\d*)?}\??)/;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
    //  Public methods
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
 | 
			
		||||
    // Lets you extend or change XRegExp syntax and create custom flags. This is used internally by
 | 
			
		||||
    // the XRegExp library and can be used to create XRegExp plugins. This function is intended for
 | 
			
		||||
    // users with advanced knowledge of JavaScript's regular expression syntax and behavior. It can
 | 
			
		||||
    // be disabled by `XRegExp.freezeTokens`
 | 
			
		||||
    XRegExp.addToken = function (regex, handler, scope, trigger) {
 | 
			
		||||
        tokens.push({
 | 
			
		||||
            pattern: clone(regex, "g" + (hasNativeY ? "y" : "")),
 | 
			
		||||
            handler: handler,
 | 
			
		||||
            scope: scope || XRegExp.OUTSIDE_CLASS,
 | 
			
		||||
            trigger: trigger || null
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Accepts a pattern and flags; returns an extended `RegExp` object. If the pattern and flag
 | 
			
		||||
    // combination has previously been cached, the cached copy is returned; otherwise the newly
 | 
			
		||||
    // created regex is cached
 | 
			
		||||
    XRegExp.cache = function (pattern, flags) {
 | 
			
		||||
        var key = pattern + "/" + (flags || "");
 | 
			
		||||
        return XRegExp.cache[key] || (XRegExp.cache[key] = XRegExp(pattern, flags));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Accepts a `RegExp` instance; returns a copy with the `/g` flag set. The copy has a fresh
 | 
			
		||||
    // `lastIndex` (set to zero). If you want to copy a regex without forcing the `global`
 | 
			
		||||
    // property, use `XRegExp(regex)`. Do not use `RegExp(regex)` because it will not preserve
 | 
			
		||||
    // special properties required for named capture
 | 
			
		||||
    XRegExp.copyAsGlobal = function (regex) {
 | 
			
		||||
        return clone(regex, "g");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Accepts a string; returns the string with regex metacharacters escaped. The returned string
 | 
			
		||||
    // can safely be used at any point within a regex to match the provided literal string. Escaped
 | 
			
		||||
    // characters are [ ] { } ( ) * + ? - . , \ ^ $ | # and whitespace
 | 
			
		||||
    XRegExp.escape = function (str) {
 | 
			
		||||
        return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Accepts a string to search, regex to search with, position to start the search within the
 | 
			
		||||
    // string (default: 0), and an optional Boolean indicating whether matches must start at-or-
 | 
			
		||||
    // after the position or at the specified position only. This function ignores the `lastIndex`
 | 
			
		||||
    // of the provided regex in its own handling, but updates the property for compatibility
 | 
			
		||||
    XRegExp.execAt = function (str, regex, pos, anchored) {
 | 
			
		||||
        var r2 = clone(regex, "g" + ((anchored && hasNativeY) ? "y" : "")),
 | 
			
		||||
            match;
 | 
			
		||||
        r2.lastIndex = pos = pos || 0;
 | 
			
		||||
        match = r2.exec(str); // Run the altered `exec` (required for `lastIndex` fix, etc.)
 | 
			
		||||
        if (anchored && match && match.index !== pos)
 | 
			
		||||
            match = null;
 | 
			
		||||
        if (regex.global)
 | 
			
		||||
            regex.lastIndex = match ? r2.lastIndex : 0;
 | 
			
		||||
        return match;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Breaks the unrestorable link to XRegExp's private list of tokens, thereby preventing
 | 
			
		||||
    // syntax and flag changes. Should be run after XRegExp and any plugins are loaded
 | 
			
		||||
    XRegExp.freezeTokens = function () {
 | 
			
		||||
        XRegExp.addToken = function () {
 | 
			
		||||
            throw Error("can't run addToken after freezeTokens");
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Accepts any value; returns a Boolean indicating whether the argument is a `RegExp` object.
 | 
			
		||||
    // Note that this is also `true` for regex literals and regexes created by the `XRegExp`
 | 
			
		||||
    // constructor. This works correctly for variables created in another frame, when `instanceof`
 | 
			
		||||
    // and `constructor` checks would fail to work as intended
 | 
			
		||||
    XRegExp.isRegExp = function (o) {
 | 
			
		||||
        return Object.prototype.toString.call(o) === "[object RegExp]";
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Executes `callback` once per match within `str`. Provides a simpler and cleaner way to
 | 
			
		||||
    // iterate over regex matches compared to the traditional approaches of subverting
 | 
			
		||||
    // `String.prototype.replace` or repeatedly calling `exec` within a `while` loop
 | 
			
		||||
    XRegExp.iterate = function (str, regex, callback, context) {
 | 
			
		||||
        var r2 = clone(regex, "g"),
 | 
			
		||||
            i = -1, match;
 | 
			
		||||
        while (match = r2.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.)
 | 
			
		||||
            if (regex.global)
 | 
			
		||||
                regex.lastIndex = r2.lastIndex; // Doing this to follow expectations if `lastIndex` is checked within `callback`
 | 
			
		||||
            callback.call(context, match, ++i, str, regex);
 | 
			
		||||
            if (r2.lastIndex === match.index)
 | 
			
		||||
                r2.lastIndex++;
 | 
			
		||||
        }
 | 
			
		||||
        if (regex.global)
 | 
			
		||||
            regex.lastIndex = 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Accepts a string and an array of regexes; returns the result of using each successive regex
 | 
			
		||||
    // to search within the matches of the previous regex. The array of regexes can also contain
 | 
			
		||||
    // objects with `regex` and `backref` properties, in which case the named or numbered back-
 | 
			
		||||
    // references specified are passed forward to the next regex or returned. E.g.:
 | 
			
		||||
    // var xregexpImgFileNames = XRegExp.matchChain(html, [
 | 
			
		||||
    //     {regex: /<img\b([^>]+)>/i, backref: 1}, // <img> tag attributes
 | 
			
		||||
    //     {regex: XRegExp('(?ix) \\s src=" (?<src> [^"]+ )'), backref: "src"}, // src attribute values
 | 
			
		||||
    //     {regex: XRegExp("^http://xregexp\\.com(/[^#?]+)", "i"), backref: 1}, // xregexp.com paths
 | 
			
		||||
    //     /[^\/]+$/ // filenames (strip directory paths)
 | 
			
		||||
    // ]);
 | 
			
		||||
    XRegExp.matchChain = function (str, chain) {
 | 
			
		||||
        return function recurseChain (values, level) {
 | 
			
		||||
            var item = chain[level].regex ? chain[level] : {regex: chain[level]},
 | 
			
		||||
                regex = clone(item.regex, "g"),
 | 
			
		||||
                matches = [], i;
 | 
			
		||||
            for (i = 0; i < values.length; i++) {
 | 
			
		||||
                XRegExp.iterate(values[i], regex, function (match) {
 | 
			
		||||
                    matches.push(item.backref ? (match[item.backref] || "") : match[0]);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            return ((level === chain.length - 1) || !matches.length) ?
 | 
			
		||||
                matches : recurseChain(matches, level + 1);
 | 
			
		||||
        }([str], 0);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
    //  New RegExp prototype methods
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
 | 
			
		||||
    // Accepts a context object and arguments array; returns the result of calling `exec` with the
 | 
			
		||||
    // first value in the arguments array. the context is ignored but is accepted for congruity
 | 
			
		||||
    // with `Function.prototype.apply`
 | 
			
		||||
    RegExp.prototype.apply = function (context, args) {
 | 
			
		||||
        return this.exec(args[0]);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Accepts a context object and string; returns the result of calling `exec` with the provided
 | 
			
		||||
    // string. the context is ignored but is accepted for congruity with `Function.prototype.call`
 | 
			
		||||
    RegExp.prototype.call = function (context, str) {
 | 
			
		||||
        return this.exec(str);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
    //  Overriden native methods
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
 | 
			
		||||
    // Adds named capture support (with backreferences returned as `result.name`), and fixes two
 | 
			
		||||
    // cross-browser issues per ES3:
 | 
			
		||||
    // - Captured values for nonparticipating capturing groups should be returned as `undefined`,
 | 
			
		||||
    //   rather than the empty string.
 | 
			
		||||
    // - `lastIndex` should not be incremented after zero-length matches.
 | 
			
		||||
    RegExp.prototype.exec = function (str) {
 | 
			
		||||
        var match, name, r2, origLastIndex;
 | 
			
		||||
        if (!this.global)
 | 
			
		||||
            origLastIndex = this.lastIndex;
 | 
			
		||||
        match = nativ.exec.apply(this, arguments);
 | 
			
		||||
        if (match) {
 | 
			
		||||
            // Fix browsers whose `exec` methods don't consistently return `undefined` for
 | 
			
		||||
            // nonparticipating capturing groups
 | 
			
		||||
            if (!compliantExecNpcg && match.length > 1 && indexOf(match, "") > -1) {
 | 
			
		||||
                r2 = RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", ""));
 | 
			
		||||
                // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed
 | 
			
		||||
                // matching due to characters outside the match
 | 
			
		||||
                nativ.replace.call((str + "").slice(match.index), r2, function () {
 | 
			
		||||
                    for (var i = 1; i < arguments.length - 2; i++) {
 | 
			
		||||
                        if (arguments[i] === undefined)
 | 
			
		||||
                            match[i] = undefined;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            // Attach named capture properties
 | 
			
		||||
            if (this._xregexp && this._xregexp.captureNames) {
 | 
			
		||||
                for (var i = 1; i < match.length; i++) {
 | 
			
		||||
                    name = this._xregexp.captureNames[i - 1];
 | 
			
		||||
                    if (name)
 | 
			
		||||
                       match[name] = match[i];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // Fix browsers that increment `lastIndex` after zero-length matches
 | 
			
		||||
            if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))
 | 
			
		||||
                this.lastIndex--;
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.global)
 | 
			
		||||
            this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)
 | 
			
		||||
        return match;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Fix browser bugs in native method
 | 
			
		||||
    RegExp.prototype.test = function (str) {
 | 
			
		||||
        // Use the native `exec` to skip some processing overhead, even though the altered
 | 
			
		||||
        // `exec` would take care of the `lastIndex` fixes
 | 
			
		||||
        var match, origLastIndex;
 | 
			
		||||
        if (!this.global)
 | 
			
		||||
            origLastIndex = this.lastIndex;
 | 
			
		||||
        match = nativ.exec.call(this, str);
 | 
			
		||||
        // Fix browsers that increment `lastIndex` after zero-length matches
 | 
			
		||||
        if (match && !compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))
 | 
			
		||||
            this.lastIndex--;
 | 
			
		||||
        if (!this.global)
 | 
			
		||||
            this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)
 | 
			
		||||
        return !!match;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Adds named capture support and fixes browser bugs in native method
 | 
			
		||||
    String.prototype.match = function (regex) {
 | 
			
		||||
        if (!XRegExp.isRegExp(regex))
 | 
			
		||||
            regex = RegExp(regex); // Native `RegExp`
 | 
			
		||||
        if (regex.global) {
 | 
			
		||||
            var result = nativ.match.apply(this, arguments);
 | 
			
		||||
            regex.lastIndex = 0; // Fix IE bug
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
        return regex.exec(this); // Run the altered `exec`
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Adds support for `${n}` tokens for named and numbered backreferences in replacement text,
 | 
			
		||||
    // and provides named backreferences to replacement functions as `arguments[0].name`. Also
 | 
			
		||||
    // fixes cross-browser differences in replacement text syntax when performing a replacement
 | 
			
		||||
    // using a nonregex search value, and the value of replacement regexes' `lastIndex` property
 | 
			
		||||
    // during replacement iterations. Note that this doesn't support SpiderMonkey's proprietary
 | 
			
		||||
    // third (`flags`) parameter
 | 
			
		||||
    String.prototype.replace = function (search, replacement) {
 | 
			
		||||
        var isRegex = XRegExp.isRegExp(search),
 | 
			
		||||
            captureNames, result, str, origLastIndex;
 | 
			
		||||
 | 
			
		||||
        // There are too many combinations of search/replacement types/values and browser bugs that
 | 
			
		||||
        // preclude passing to native `replace`, so don't try
 | 
			
		||||
        //if (...)
 | 
			
		||||
        //    return nativ.replace.apply(this, arguments);
 | 
			
		||||
 | 
			
		||||
        if (isRegex) {
 | 
			
		||||
            if (search._xregexp)
 | 
			
		||||
                captureNames = search._xregexp.captureNames; // Array or `null`
 | 
			
		||||
            if (!search.global)
 | 
			
		||||
                origLastIndex = search.lastIndex;
 | 
			
		||||
        } else {
 | 
			
		||||
            search = search + ""; // Type conversion
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Object.prototype.toString.call(replacement) === "[object Function]") {
 | 
			
		||||
            result = nativ.replace.call(this + "", search, function () {
 | 
			
		||||
                if (captureNames) {
 | 
			
		||||
                    // Change the `arguments[0]` string primitive to a String object which can store properties
 | 
			
		||||
                    arguments[0] = new String(arguments[0]);
 | 
			
		||||
                    // Store named backreferences on `arguments[0]`
 | 
			
		||||
                    for (var i = 0; i < captureNames.length; i++) {
 | 
			
		||||
                        if (captureNames[i])
 | 
			
		||||
                            arguments[0][captureNames[i]] = arguments[i + 1];
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                // Update `lastIndex` before calling `replacement` (fix browsers)
 | 
			
		||||
                if (isRegex && search.global)
 | 
			
		||||
                    search.lastIndex = arguments[arguments.length - 2] + arguments[0].length;
 | 
			
		||||
                return replacement.apply(null, arguments);
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            str = this + ""; // Type conversion, so `args[args.length - 1]` will be a string (given nonstring `this`)
 | 
			
		||||
            result = nativ.replace.call(str, search, function () {
 | 
			
		||||
                var args = arguments; // Keep this function's `arguments` available through closure
 | 
			
		||||
                return nativ.replace.call(replacement + "", replacementToken, function ($0, $1, $2) {
 | 
			
		||||
                    // Numbered backreference (without delimiters) or special variable
 | 
			
		||||
                    if ($1) {
 | 
			
		||||
                        switch ($1) {
 | 
			
		||||
                            case "$": return "$";
 | 
			
		||||
                            case "&": return args[0];
 | 
			
		||||
                            case "`": return args[args.length - 1].slice(0, args[args.length - 2]);
 | 
			
		||||
                            case "'": return args[args.length - 1].slice(args[args.length - 2] + args[0].length);
 | 
			
		||||
                            // Numbered backreference
 | 
			
		||||
                            default:
 | 
			
		||||
                                // What does "$10" mean?
 | 
			
		||||
                                // - Backreference 10, if 10 or more capturing groups exist
 | 
			
		||||
                                // - Backreference 1 followed by "0", if 1-9 capturing groups exist
 | 
			
		||||
                                // - Otherwise, it's the string "$10"
 | 
			
		||||
                                // Also note:
 | 
			
		||||
                                // - Backreferences cannot be more than two digits (enforced by `replacementToken`)
 | 
			
		||||
                                // - "$01" is equivalent to "$1" if a capturing group exists, otherwise it's the string "$01"
 | 
			
		||||
                                // - There is no "$0" token ("$&" is the entire match)
 | 
			
		||||
                                var literalNumbers = "";
 | 
			
		||||
                                $1 = +$1; // Type conversion; drop leading zero
 | 
			
		||||
                                if (!$1) // `$1` was "0" or "00"
 | 
			
		||||
                                    return $0;
 | 
			
		||||
                                while ($1 > args.length - 3) {
 | 
			
		||||
                                    literalNumbers = String.prototype.slice.call($1, -1) + literalNumbers;
 | 
			
		||||
                                    $1 = Math.floor($1 / 10); // Drop the last digit
 | 
			
		||||
                                }
 | 
			
		||||
                                return ($1 ? args[$1] || "" : "$") + literalNumbers;
 | 
			
		||||
                        }
 | 
			
		||||
                    // Named backreference or delimited numbered backreference
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // What does "${n}" mean?
 | 
			
		||||
                        // - Backreference to numbered capture n. Two differences from "$n":
 | 
			
		||||
                        //   - n can be more than two digits
 | 
			
		||||
                        //   - Backreference 0 is allowed, and is the entire match
 | 
			
		||||
                        // - Backreference to named capture n, if it exists and is not a number overridden by numbered capture
 | 
			
		||||
                        // - Otherwise, it's the string "${n}"
 | 
			
		||||
                        var n = +$2; // Type conversion; drop leading zeros
 | 
			
		||||
                        if (n <= args.length - 3)
 | 
			
		||||
                            return args[n];
 | 
			
		||||
                        n = captureNames ? indexOf(captureNames, $2) : -1;
 | 
			
		||||
                        return n > -1 ? args[n + 1] : $0;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isRegex) {
 | 
			
		||||
            if (search.global)
 | 
			
		||||
                search.lastIndex = 0; // Fix IE, Safari bug (last tested IE 9.0.5, Safari 5.1.2 on Windows)
 | 
			
		||||
            else
 | 
			
		||||
                search.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // A consistent cross-browser, ES3 compliant `split`
 | 
			
		||||
    String.prototype.split = function (s /* separator */, limit) {
 | 
			
		||||
        // If separator `s` is not a regex, use the native `split`
 | 
			
		||||
        if (!XRegExp.isRegExp(s))
 | 
			
		||||
            return nativ.split.apply(this, arguments);
 | 
			
		||||
 | 
			
		||||
        var str = this + "", // Type conversion
 | 
			
		||||
            output = [],
 | 
			
		||||
            lastLastIndex = 0,
 | 
			
		||||
            match, lastLength;
 | 
			
		||||
 | 
			
		||||
        // Behavior for `limit`: if it's...
 | 
			
		||||
        // - `undefined`: No limit
 | 
			
		||||
        // - `NaN` or zero: Return an empty array
 | 
			
		||||
        // - A positive number: Use `Math.floor(limit)`
 | 
			
		||||
        // - A negative number: No limit
 | 
			
		||||
        // - Other: Type-convert, then use the above rules
 | 
			
		||||
        if (limit === undefined || +limit < 0) {
 | 
			
		||||
            limit = Infinity;
 | 
			
		||||
        } else {
 | 
			
		||||
            limit = Math.floor(+limit);
 | 
			
		||||
            if (!limit)
 | 
			
		||||
                return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // This is required if not `s.global`, and it avoids needing to set `s.lastIndex` to zero
 | 
			
		||||
        // and restore it to its original value when we're done using the regex
 | 
			
		||||
        s = XRegExp.copyAsGlobal(s);
 | 
			
		||||
 | 
			
		||||
        while (match = s.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.)
 | 
			
		||||
            if (s.lastIndex > lastLastIndex) {
 | 
			
		||||
                output.push(str.slice(lastLastIndex, match.index));
 | 
			
		||||
 | 
			
		||||
                if (match.length > 1 && match.index < str.length)
 | 
			
		||||
                    Array.prototype.push.apply(output, match.slice(1));
 | 
			
		||||
 | 
			
		||||
                lastLength = match[0].length;
 | 
			
		||||
                lastLastIndex = s.lastIndex;
 | 
			
		||||
 | 
			
		||||
                if (output.length >= limit)
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (s.lastIndex === match.index)
 | 
			
		||||
                s.lastIndex++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (lastLastIndex === str.length) {
 | 
			
		||||
            if (!nativ.test.call(s, "") || lastLength)
 | 
			
		||||
                output.push("");
 | 
			
		||||
        } else {
 | 
			
		||||
            output.push(str.slice(lastLastIndex));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return output.length > limit ? output.slice(0, limit) : output;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
    //  Private helper functions
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
 | 
			
		||||
    // Supporting function for `XRegExp`, `XRegExp.copyAsGlobal`, etc. Returns a copy of a `RegExp`
 | 
			
		||||
    // instance with a fresh `lastIndex` (set to zero), preserving properties required for named
 | 
			
		||||
    // capture. Also allows adding new flags in the process of copying the regex
 | 
			
		||||
    function clone (regex, additionalFlags) {
 | 
			
		||||
        if (!XRegExp.isRegExp(regex))
 | 
			
		||||
            throw TypeError("type RegExp expected");
 | 
			
		||||
        var x = regex._xregexp;
 | 
			
		||||
        regex = XRegExp(regex.source, getNativeFlags(regex) + (additionalFlags || ""));
 | 
			
		||||
        if (x) {
 | 
			
		||||
            regex._xregexp = {
 | 
			
		||||
                source: x.source,
 | 
			
		||||
                captureNames: x.captureNames ? x.captureNames.slice(0) : null
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        return regex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getNativeFlags (regex) {
 | 
			
		||||
        return (regex.global     ? "g" : "") +
 | 
			
		||||
               (regex.ignoreCase ? "i" : "") +
 | 
			
		||||
               (regex.multiline  ? "m" : "") +
 | 
			
		||||
               (regex.extended   ? "x" : "") + // Proposed for ES4; included in AS3
 | 
			
		||||
               (regex.sticky     ? "y" : "");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function runTokens (pattern, index, scope, context) {
 | 
			
		||||
        var i = tokens.length,
 | 
			
		||||
            result, match, t;
 | 
			
		||||
        // Protect against constructing XRegExps within token handler and trigger functions
 | 
			
		||||
        isInsideConstructor = true;
 | 
			
		||||
        // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws
 | 
			
		||||
        try {
 | 
			
		||||
            while (i--) { // Run in reverse order
 | 
			
		||||
                t = tokens[i];
 | 
			
		||||
                if ((scope & t.scope) && (!t.trigger || t.trigger.call(context))) {
 | 
			
		||||
                    t.pattern.lastIndex = index;
 | 
			
		||||
                    match = t.pattern.exec(pattern); // Running the altered `exec` here allows use of named backreferences, etc.
 | 
			
		||||
                    if (match && match.index === index) {
 | 
			
		||||
                        result = {
 | 
			
		||||
                            output: t.handler.call(context, match, scope),
 | 
			
		||||
                            match: match
 | 
			
		||||
                        };
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            throw err;
 | 
			
		||||
        } finally {
 | 
			
		||||
            isInsideConstructor = false;
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function indexOf (array, item, from) {
 | 
			
		||||
        if (Array.prototype.indexOf) // Use the native array method if available
 | 
			
		||||
            return array.indexOf(item, from);
 | 
			
		||||
        for (var i = from || 0; i < array.length; i++) {
 | 
			
		||||
            if (array[i] === item)
 | 
			
		||||
                return i;
 | 
			
		||||
        }
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
    //  Built-in tokens
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
 | 
			
		||||
    // Augment XRegExp's regular expression syntax and flags. Note that when adding tokens, the
 | 
			
		||||
    // third (`scope`) argument defaults to `XRegExp.OUTSIDE_CLASS`
 | 
			
		||||
 | 
			
		||||
    // Comment pattern: (?# )
 | 
			
		||||
    XRegExp.addToken(
 | 
			
		||||
        /\(\?#[^)]*\)/,
 | 
			
		||||
        function (match) {
 | 
			
		||||
            // Keep tokens separated unless the following token is a quantifier
 | 
			
		||||
            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Capturing group (match the opening parenthesis only).
 | 
			
		||||
    // Required for support of named capturing groups
 | 
			
		||||
    XRegExp.addToken(
 | 
			
		||||
        /\((?!\?)/,
 | 
			
		||||
        function () {
 | 
			
		||||
            this.captureNames.push(null);
 | 
			
		||||
            return "(";
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Named capturing group (match the opening delimiter only): (?<name>
 | 
			
		||||
    XRegExp.addToken(
 | 
			
		||||
        /\(\?<([$\w]+)>/,
 | 
			
		||||
        function (match) {
 | 
			
		||||
            this.captureNames.push(match[1]);
 | 
			
		||||
            this.hasNamedCapture = true;
 | 
			
		||||
            return "(";
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Named backreference: \k<name>
 | 
			
		||||
    XRegExp.addToken(
 | 
			
		||||
        /\\k<([\w$]+)>/,
 | 
			
		||||
        function (match) {
 | 
			
		||||
            var index = indexOf(this.captureNames, match[1]);
 | 
			
		||||
            // Keep backreferences separate from subsequent literal numbers. Preserve back-
 | 
			
		||||
            // references to named groups that are undefined at this point as literal strings
 | 
			
		||||
            return index > -1 ?
 | 
			
		||||
                "\\" + (index + 1) + (isNaN(match.input.charAt(match.index + match[0].length)) ? "" : "(?:)") :
 | 
			
		||||
                match[0];
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Empty character class: [] or [^]
 | 
			
		||||
    XRegExp.addToken(
 | 
			
		||||
        /\[\^?]/,
 | 
			
		||||
        function (match) {
 | 
			
		||||
            // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S].
 | 
			
		||||
            // (?!) should work like \b\B, but is unreliable in Firefox
 | 
			
		||||
            return match[0] === "[]" ? "\\b\\B" : "[\\s\\S]";
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Mode modifier at the start of the pattern only, with any combination of flags imsx: (?imsx)
 | 
			
		||||
    // Does not support x(?i), (?-i), (?i-m), (?i: ), (?i)(?m), etc.
 | 
			
		||||
    XRegExp.addToken(
 | 
			
		||||
        /^\(\?([imsx]+)\)/,
 | 
			
		||||
        function (match) {
 | 
			
		||||
            this.setFlag(match[1]);
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Whitespace and comments, in free-spacing (aka extended) mode only
 | 
			
		||||
    XRegExp.addToken(
 | 
			
		||||
        /(?:\s+|#.*)+/,
 | 
			
		||||
        function (match) {
 | 
			
		||||
            // Keep tokens separated unless the following token is a quantifier
 | 
			
		||||
            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
 | 
			
		||||
        },
 | 
			
		||||
        XRegExp.OUTSIDE_CLASS,
 | 
			
		||||
        function () {return this.hasFlag("x");}
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Dot, in dotall (aka singleline) mode only
 | 
			
		||||
    XRegExp.addToken(
 | 
			
		||||
        /\./,
 | 
			
		||||
        function () {return "[\\s\\S]";},
 | 
			
		||||
        XRegExp.OUTSIDE_CLASS,
 | 
			
		||||
        function () {return this.hasFlag("s");}
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
    //  Backward compatibility
 | 
			
		||||
    //---------------------------------
 | 
			
		||||
 | 
			
		||||
    // Uncomment the following block for compatibility with XRegExp 1.0-1.2:
 | 
			
		||||
    /*
 | 
			
		||||
    XRegExp.matchWithinChain = XRegExp.matchChain;
 | 
			
		||||
    RegExp.prototype.addFlags = function (s) {return clone(this, s);};
 | 
			
		||||
    RegExp.prototype.execAll = function (s) {var r = []; XRegExp.iterate(s, this, function (m) {r.push(m);}); return r;};
 | 
			
		||||
    RegExp.prototype.forEachExec = function (s, f, c) {return XRegExp.iterate(s, this, f, c);};
 | 
			
		||||
    RegExp.prototype.validate = function (s) {var r = RegExp("^(?:" + this.source + ")$(?!\\s)", getNativeFlags(this)); if (this.global) this.lastIndex = 0; return s.search(r) === 0;};
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
import os
 | 
			
		||||
from mimetypes import guess_type
 | 
			
		||||
from contextlib import closing
 | 
			
		||||
from base64 import b64encode
 | 
			
		||||
 | 
			
		||||
from pecan.compat import quote
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_resource(filename):
 | 
			
		||||
    with closing(open(
 | 
			
		||||
        os.path.join(
 | 
			
		||||
            os.path.dirname(__file__),
 | 
			
		||||
            filename
 | 
			
		||||
        ),
 | 
			
		||||
        'rb'
 | 
			
		||||
    )) as f:
 | 
			
		||||
        data = f.read()
 | 
			
		||||
        return 'data:%s;base64,%s' % (
 | 
			
		||||
            guess_type(filename)[0],
 | 
			
		||||
            quote(b64encode(data))
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
pecan_image = load_resource('pecan.png')
 | 
			
		||||
xregexp_js = load_resource('XRegExp.js')
 | 
			
		||||
syntax_js = load_resource('shCore.js')
 | 
			
		||||
syntax_css = load_resource('syntax.css')
 | 
			
		||||
theme = load_resource('theme.css')
 | 
			
		||||
brush = load_resource('brush-python.js')
 | 
			
		||||
@@ -1,64 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * SyntaxHighlighter
 | 
			
		||||
 * http://alexgorbatchev.com/SyntaxHighlighter
 | 
			
		||||
 *
 | 
			
		||||
 * SyntaxHighlighter is donationware. If you are using it, please donate.
 | 
			
		||||
 * http://alexgorbatchev.com/SyntaxHighlighter/donate.html
 | 
			
		||||
 *
 | 
			
		||||
 * @version
 | 
			
		||||
 * 3.0.83 (July 02 2010)
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright
 | 
			
		||||
 * Copyright (C) 2004-2010 Alex Gorbatchev.
 | 
			
		||||
 *
 | 
			
		||||
 * @license
 | 
			
		||||
 * Dual licensed under the MIT and GPL licenses.
 | 
			
		||||
 */
 | 
			
		||||
(function()
 | 
			
		||||
{
 | 
			
		||||
	// CommonJS
 | 
			
		||||
	typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
 | 
			
		||||
 | 
			
		||||
	function Brush()
 | 
			
		||||
	{
 | 
			
		||||
		// Contributed by Gheorghe Milas and Ahmad Sherif
 | 
			
		||||
 | 
			
		||||
		var keywords =  'and assert break class continue def del elif else ' +
 | 
			
		||||
						'except exec finally for from global if import in is ' +
 | 
			
		||||
						'lambda not or pass print raise return try yield while';
 | 
			
		||||
 | 
			
		||||
		var funcs = '__import__ abs all any apply basestring bin bool buffer callable ' +
 | 
			
		||||
					'chr classmethod cmp coerce compile complex delattr dict dir ' +
 | 
			
		||||
					'divmod enumerate eval execfile file filter float format frozenset ' +
 | 
			
		||||
					'getattr globals hasattr hash help hex id input int intern ' +
 | 
			
		||||
					'isinstance issubclass iter len list locals long map max min next ' +
 | 
			
		||||
					'object oct open ord pow print property range raw_input reduce ' +
 | 
			
		||||
					'reload repr reversed round set setattr slice sorted staticmethod ' +
 | 
			
		||||
					'str sum super tuple type type unichr unicode vars xrange zip';
 | 
			
		||||
 | 
			
		||||
		var special =  'None True False self cls class_';
 | 
			
		||||
 | 
			
		||||
		this.regexList = [
 | 
			
		||||
				{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' },
 | 
			
		||||
				{ regex: /^\s*@\w+/gm, 										css: 'decorator' },
 | 
			
		||||
				{ regex: /(['\"]{3})([^\1])*?\1/gm, 						css: 'comments' },
 | 
			
		||||
				{ regex: /"(?!")(?:\.|\\\"|[^\""\n])*"/gm, 					css: 'string' },
 | 
			
		||||
				{ regex: /'(?!')(?:\.|(\\\')|[^\''\n])*'/gm, 				css: 'string' },
 | 
			
		||||
				{ regex: /\+|\-|\*|\/|\%|=|==/gm, 							css: 'keyword' },
 | 
			
		||||
				{ regex: /\b\d+\.?\w*/g, 									css: 'value' },
 | 
			
		||||
				{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),		css: 'functions' },
 | 
			
		||||
				{ regex: new RegExp(this.getKeywords(keywords), 'gm'), 		css: 'keyword' },
 | 
			
		||||
				{ regex: new RegExp(this.getKeywords(special), 'gm'), 		css: 'color1' }
 | 
			
		||||
				];
 | 
			
		||||
 | 
			
		||||
		this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	Brush.prototype	= new SyntaxHighlighter.Highlighter();
 | 
			
		||||
	Brush.aliases	= ['py', 'python'];
 | 
			
		||||
 | 
			
		||||
	SyntaxHighlighter.brushes.Python = Brush;
 | 
			
		||||
 | 
			
		||||
	// CommonJS
 | 
			
		||||
	typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
 | 
			
		||||
})();
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 4.3 KiB  | 
							
								
								
									
										1731
									
								
								pecan/middleware/resources/shCore.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1731
									
								
								pecan/middleware/resources/shCore.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,226 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * SyntaxHighlighter
 | 
			
		||||
 * http://alexgorbatchev.com/SyntaxHighlighter
 | 
			
		||||
 *
 | 
			
		||||
 * SyntaxHighlighter is donationware. If you are using it, please donate.
 | 
			
		||||
 * http://alexgorbatchev.com/SyntaxHighlighter/donate.html
 | 
			
		||||
 *
 | 
			
		||||
 * @version
 | 
			
		||||
 * 3.0.83 (July 02 2010)
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright
 | 
			
		||||
 * Copyright (C) 2004-2010 Alex Gorbatchev.
 | 
			
		||||
 *
 | 
			
		||||
 * @license
 | 
			
		||||
 * Dual licensed under the MIT and GPL licenses.
 | 
			
		||||
 */
 | 
			
		||||
.syntaxhighlighter a,
 | 
			
		||||
.syntaxhighlighter div,
 | 
			
		||||
.syntaxhighlighter code,
 | 
			
		||||
.syntaxhighlighter table,
 | 
			
		||||
.syntaxhighlighter table td,
 | 
			
		||||
.syntaxhighlighter table tr,
 | 
			
		||||
.syntaxhighlighter table tbody,
 | 
			
		||||
.syntaxhighlighter table thead,
 | 
			
		||||
.syntaxhighlighter table caption,
 | 
			
		||||
.syntaxhighlighter textarea {
 | 
			
		||||
  -moz-border-radius: 0 0 0 0 !important;
 | 
			
		||||
  -webkit-border-radius: 0 0 0 0 !important;
 | 
			
		||||
  background: none !important;
 | 
			
		||||
  border: 0 !important;
 | 
			
		||||
  bottom: auto !important;
 | 
			
		||||
  float: none !important;
 | 
			
		||||
  height: auto !important;
 | 
			
		||||
  left: auto !important;
 | 
			
		||||
  line-height: 1.1em !important;
 | 
			
		||||
  margin: 0 !important;
 | 
			
		||||
  outline: 0 !important;
 | 
			
		||||
  overflow: visible !important;
 | 
			
		||||
  padding: 0 !important;
 | 
			
		||||
  position: static !important;
 | 
			
		||||
  right: auto !important;
 | 
			
		||||
  text-align: left !important;
 | 
			
		||||
  top: auto !important;
 | 
			
		||||
  vertical-align: baseline !important;
 | 
			
		||||
  width: auto !important;
 | 
			
		||||
  box-sizing: content-box !important;
 | 
			
		||||
  font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important;
 | 
			
		||||
  font-weight: normal !important;
 | 
			
		||||
  font-style: normal !important;
 | 
			
		||||
  font-size: 1em !important;
 | 
			
		||||
  min-height: inherit !important;
 | 
			
		||||
  min-height: auto !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.syntaxhighlighter {
 | 
			
		||||
  width: 100% !important;
 | 
			
		||||
  margin: 1em 0 1em 0 !important;
 | 
			
		||||
  position: relative !important;
 | 
			
		||||
  overflow: auto !important;
 | 
			
		||||
  font-size: 1em !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.source {
 | 
			
		||||
  overflow: hidden !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .bold {
 | 
			
		||||
  font-weight: bold !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .italic {
 | 
			
		||||
  font-style: italic !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .line {
 | 
			
		||||
  white-space: pre !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter table {
 | 
			
		||||
  width: 100% !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter table caption {
 | 
			
		||||
  text-align: left !important;
 | 
			
		||||
  padding: .5em 0 0.5em 1em !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter table td.code {
 | 
			
		||||
  width: 100% !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter table td.code .container {
 | 
			
		||||
  position: relative !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter table td.code .container textarea {
 | 
			
		||||
  box-sizing: border-box !important;
 | 
			
		||||
  position: absolute !important;
 | 
			
		||||
  left: 0 !important;
 | 
			
		||||
  top: 0 !important;
 | 
			
		||||
  width: 100% !important;
 | 
			
		||||
  height: 100% !important;
 | 
			
		||||
  border: none !important;
 | 
			
		||||
  background: white !important;
 | 
			
		||||
  padding-left: 1em !important;
 | 
			
		||||
  overflow: hidden !important;
 | 
			
		||||
  white-space: pre !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter table td.gutter .line {
 | 
			
		||||
  text-align: right !important;
 | 
			
		||||
  padding: 0 0.5em 0 1em !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter table td.code .line {
 | 
			
		||||
  padding: 0 1em !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line {
 | 
			
		||||
  padding-left: 0em !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.show {
 | 
			
		||||
  display: block !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.collapsed table {
 | 
			
		||||
  display: none !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.collapsed .toolbar {
 | 
			
		||||
  padding: 0.1em 0.8em 0em 0.8em !important;
 | 
			
		||||
  font-size: 1em !important;
 | 
			
		||||
  position: static !important;
 | 
			
		||||
  width: auto !important;
 | 
			
		||||
  height: auto !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.collapsed .toolbar span {
 | 
			
		||||
  display: inline !important;
 | 
			
		||||
  margin-right: 1em !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.collapsed .toolbar span a {
 | 
			
		||||
  padding: 0 !important;
 | 
			
		||||
  display: none !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.collapsed .toolbar span a.expandSource {
 | 
			
		||||
  display: inline !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .toolbar {
 | 
			
		||||
  position: absolute !important;
 | 
			
		||||
  right: 1px !important;
 | 
			
		||||
  top: 1px !important;
 | 
			
		||||
  width: 11px !important;
 | 
			
		||||
  height: 11px !important;
 | 
			
		||||
  font-size: 10px !important;
 | 
			
		||||
  z-index: 10 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .toolbar span.title {
 | 
			
		||||
  display: inline !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .toolbar a {
 | 
			
		||||
  display: block !important;
 | 
			
		||||
  text-align: center !important;
 | 
			
		||||
  text-decoration: none !important;
 | 
			
		||||
  padding-top: 1px !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .toolbar a.expandSource {
 | 
			
		||||
  display: none !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.ie {
 | 
			
		||||
  font-size: .9em !important;
 | 
			
		||||
  padding: 1px 0 1px 0 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.ie .toolbar {
 | 
			
		||||
  line-height: 8px !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.ie .toolbar a {
 | 
			
		||||
  padding-top: 0px !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .line.alt1 .content,
 | 
			
		||||
.syntaxhighlighter.printing .line.alt2 .content,
 | 
			
		||||
.syntaxhighlighter.printing .line.highlighted .number,
 | 
			
		||||
.syntaxhighlighter.printing .line.highlighted.alt1 .content,
 | 
			
		||||
.syntaxhighlighter.printing .line.highlighted.alt2 .content {
 | 
			
		||||
  background: none !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .line .number {
 | 
			
		||||
  color: #bbbbbb !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .line .content {
 | 
			
		||||
  color: black !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .toolbar {
 | 
			
		||||
  display: none !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing a {
 | 
			
		||||
  text-decoration: none !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a {
 | 
			
		||||
  color: black !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a {
 | 
			
		||||
  color: #008200 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a {
 | 
			
		||||
  color: blue !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .keyword {
 | 
			
		||||
  color: #006699 !important;
 | 
			
		||||
  font-weight: bold !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .preprocessor {
 | 
			
		||||
  color: gray !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .variable {
 | 
			
		||||
  color: #aa7700 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .value {
 | 
			
		||||
  color: #009900 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .functions {
 | 
			
		||||
  color: #ff1493 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .constants {
 | 
			
		||||
  color: #0066cc !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .script {
 | 
			
		||||
  font-weight: bold !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a {
 | 
			
		||||
  color: gray !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a {
 | 
			
		||||
  color: #ff1493 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a {
 | 
			
		||||
  color: red !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a {
 | 
			
		||||
  color: black !important;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,117 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * SyntaxHighlighter
 | 
			
		||||
 * http://alexgorbatchev.com/SyntaxHighlighter
 | 
			
		||||
 *
 | 
			
		||||
 * SyntaxHighlighter is donationware. If you are using it, please donate.
 | 
			
		||||
 * http://alexgorbatchev.com/SyntaxHighlighter/donate.html
 | 
			
		||||
 *
 | 
			
		||||
 * @version
 | 
			
		||||
 * 3.0.83 (July 02 2010)
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright
 | 
			
		||||
 * Copyright (C) 2004-2010 Alex Gorbatchev.
 | 
			
		||||
 *
 | 
			
		||||
 * @license
 | 
			
		||||
 * Dual licensed under the MIT and GPL licenses.
 | 
			
		||||
 */
 | 
			
		||||
.syntaxhighlighter {
 | 
			
		||||
  background-color: white !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .line.alt1 {
 | 
			
		||||
  background-color: white !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .line.alt2 {
 | 
			
		||||
  background-color: white !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 {
 | 
			
		||||
  background-color: #e0e0e0 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .line.highlighted.number {
 | 
			
		||||
  color: black !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter table caption {
 | 
			
		||||
  color: black !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .gutter {
 | 
			
		||||
  color: #afafaf !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .gutter .line {
 | 
			
		||||
  border-right: 3px solid #6ce26c !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .gutter .line.highlighted {
 | 
			
		||||
  background-color: #6ce26c !important;
 | 
			
		||||
  color: white !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.printing .line .content {
 | 
			
		||||
  border: none !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.collapsed {
 | 
			
		||||
  overflow: visible !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.collapsed .toolbar {
 | 
			
		||||
  color: blue !important;
 | 
			
		||||
  background: white !important;
 | 
			
		||||
  border: 1px solid #6ce26c !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.collapsed .toolbar a {
 | 
			
		||||
  color: blue !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter.collapsed .toolbar a:hover {
 | 
			
		||||
  color: red !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .toolbar {
 | 
			
		||||
  color: white !important;
 | 
			
		||||
  background: #6ce26c !important;
 | 
			
		||||
  border: none !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .toolbar a {
 | 
			
		||||
  color: white !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .toolbar a:hover {
 | 
			
		||||
  color: black !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .plain, .syntaxhighlighter .plain a {
 | 
			
		||||
  color: black !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .comments, .syntaxhighlighter .comments a {
 | 
			
		||||
  color: #008200 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .string, .syntaxhighlighter .string a {
 | 
			
		||||
  color: blue !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .keyword {
 | 
			
		||||
  color: #006699 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .preprocessor {
 | 
			
		||||
  color: gray !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .variable {
 | 
			
		||||
  color: #aa7700 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .value {
 | 
			
		||||
  color: #009900 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .functions {
 | 
			
		||||
  color: #ff1493 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .constants {
 | 
			
		||||
  color: #0066cc !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .script {
 | 
			
		||||
  font-weight: bold !important;
 | 
			
		||||
  color: #006699 !important;
 | 
			
		||||
  background-color: none !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
 | 
			
		||||
  color: gray !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .color2, .syntaxhighlighter .color2 a {
 | 
			
		||||
  color: #ff1493 !important;
 | 
			
		||||
}
 | 
			
		||||
.syntaxhighlighter .color3, .syntaxhighlighter .color3 a {
 | 
			
		||||
  color: red !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.syntaxhighlighter .keyword {
 | 
			
		||||
  font-weight: bold !important;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,83 +0,0 @@
 | 
			
		||||
from wsgiref.util import setup_testing_defaults
 | 
			
		||||
 | 
			
		||||
from webtest import TestApp
 | 
			
		||||
from six import b as b_
 | 
			
		||||
 | 
			
		||||
from pecan.middleware.debug import DebugMiddleware
 | 
			
		||||
from pecan.tests import PecanTestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StripPasteVar(object):
 | 
			
		||||
    def __init__(self, app):
 | 
			
		||||
        self.app = app
 | 
			
		||||
 | 
			
		||||
    def __call__(self, environ, start_response):
 | 
			
		||||
        environ.pop('paste.testing')
 | 
			
		||||
        return self.app(environ, start_response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestDebugMiddleware(PecanTestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestDebugMiddleware, self).setUp()
 | 
			
		||||
 | 
			
		||||
        def conditional_error_app(environ, start_response):
 | 
			
		||||
            setup_testing_defaults(environ)
 | 
			
		||||
            if environ['PATH_INFO'] == '/error':
 | 
			
		||||
                assert 1 == 2
 | 
			
		||||
            start_response("200 OK", [('Content-type', 'text/plain')])
 | 
			
		||||
            return [b_('requested page returned')]
 | 
			
		||||
        self.app = TestApp(StripPasteVar(DebugMiddleware(
 | 
			
		||||
            conditional_error_app
 | 
			
		||||
        )))
 | 
			
		||||
 | 
			
		||||
    def test_middleware_passes_through_when_no_exception_raised(self):
 | 
			
		||||
        r = self.app.get('/')
 | 
			
		||||
        assert r.status_int == 200
 | 
			
		||||
        assert r.body == b_('requested page returned')
 | 
			
		||||
 | 
			
		||||
    def test_middleware_gives_stack_trace_on_errors(self):
 | 
			
		||||
        r = self.app.get('/error', expect_errors=True)
 | 
			
		||||
        assert r.status_int == 400
 | 
			
		||||
        assert b_('AssertionError') in r.body
 | 
			
		||||
 | 
			
		||||
    def test_middleware_complains_in_multi_process_environment(self):
 | 
			
		||||
 | 
			
		||||
        class MultiProcessApp(object):
 | 
			
		||||
 | 
			
		||||
            def __init__(self, app):
 | 
			
		||||
                self.app = app
 | 
			
		||||
 | 
			
		||||
            def __call__(self, environ, start_response):
 | 
			
		||||
                environ['wsgi.multiprocess'] = True
 | 
			
		||||
                return self.app(environ, start_response)
 | 
			
		||||
 | 
			
		||||
        def conditional_error_app(environ, start_response):
 | 
			
		||||
            start_response("200 OK", [('Content-type', 'text/plain')])
 | 
			
		||||
            return ['Hello, World!']
 | 
			
		||||
 | 
			
		||||
        app = TestApp(MultiProcessApp(DebugMiddleware(conditional_error_app)))
 | 
			
		||||
        self.assertRaises(
 | 
			
		||||
            RuntimeError,
 | 
			
		||||
            app.get,
 | 
			
		||||
            '/'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_middlware_allows_for_post_mortem_debugging(self):
 | 
			
		||||
        def patch_debugger(d):
 | 
			
		||||
            def _patched_debug_request():
 | 
			
		||||
                d.append(True)
 | 
			
		||||
            return _patched_debug_request
 | 
			
		||||
 | 
			
		||||
        debugger = []
 | 
			
		||||
 | 
			
		||||
        app = TestApp(StripPasteVar(DebugMiddleware(
 | 
			
		||||
            self.app,
 | 
			
		||||
            patch_debugger(debugger)
 | 
			
		||||
        )))
 | 
			
		||||
 | 
			
		||||
        r = app.get('/error', expect_errors=True)
 | 
			
		||||
        assert r.status_int == 400
 | 
			
		||||
 | 
			
		||||
        r = app.get('/__pecan_initiate_pdb__', expect_errors=True)
 | 
			
		||||
        assert len(debugger) > 0
 | 
			
		||||
@@ -1683,41 +1683,6 @@ class TestNonCanonical(PecanTestCase):
 | 
			
		||||
        assert len(wrapped_apps) == 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestDebugging(PecanTestCase):
 | 
			
		||||
    def test_debugger_setup(self):
 | 
			
		||||
        class RootController(object):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        def debugger():
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        app_conf = dict(
 | 
			
		||||
            debug=True,
 | 
			
		||||
            debugger=debugger
 | 
			
		||||
        )
 | 
			
		||||
        with mock.patch('pecan.middleware.debug.DebugMiddleware') \
 | 
			
		||||
                as patched_debug_middleware:
 | 
			
		||||
            app = make_app(RootController(), **app_conf)
 | 
			
		||||
            args, kwargs = patched_debug_middleware.call_args
 | 
			
		||||
            assert kwargs.get('debugger') == debugger
 | 
			
		||||
 | 
			
		||||
    def test_invalid_debugger_setup(self):
 | 
			
		||||
        class RootController(object):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        debugger = 'not_a_valid_entry_point'
 | 
			
		||||
 | 
			
		||||
        app_conf = dict(
 | 
			
		||||
            debug=True,
 | 
			
		||||
            debugger=debugger
 | 
			
		||||
        )
 | 
			
		||||
        with mock.patch('pecan.middleware.debug.DebugMiddleware') \
 | 
			
		||||
                as patched_debug_middleware:
 | 
			
		||||
            app = make_app(RootController(), **app_conf)
 | 
			
		||||
            args, kwargs = patched_debug_middleware.call_args
 | 
			
		||||
            assert kwargs.get('debugger') is None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestLogging(PecanTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_logging_setup(self):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user