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:
Ryan Petrello
2015-06-04 13:43:31 -04:00
parent d3159f1d6b
commit 177c566eca
16 changed files with 106 additions and 3301 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -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. Youll 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
--------------------

View File

@@ -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:

View File

@@ -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

View File

@@ -1,4 +1,3 @@
from . import debug
from . import errordocument
from . import recursive
from . import static

View File

@@ -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)

View File

@@ -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;};
*/
})();

View File

@@ -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')

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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):