From bd33f1170e3241e2b5e061e44ec05b8d175e5739 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 18 Apr 2008 09:17:32 +0200 Subject: [PATCH] added C escape and tb_set_next functions --HG-- branch : trunk --- jinja2/_speedups.c | 216 +++++++++++++++++++++++++++++++++++++++++++++ jinja2/debug.py | 11 ++- jinja2/utils.py | 34 ++++--- setup.py | 37 +++++++- 4 files changed, 278 insertions(+), 20 deletions(-) create mode 100644 jinja2/_speedups.c diff --git a/jinja2/_speedups.c b/jinja2/_speedups.c new file mode 100644 index 0000000..30bea54 --- /dev/null +++ b/jinja2/_speedups.c @@ -0,0 +1,216 @@ +/** + * jinja2._speedups + * ~~~~~~~~~~~~~~~~ + * + * This module implements a few functions in C for better performance. + * + * :copyright: 2008 by Armin Ronacher. + * :license: BSD. + */ + +#include + + +static char *samp = "&", *slt = "<", *sgt = ">", *sqt = """; +static Py_UNICODE *amp, *lt, *gt, *qt; +static PyObject* markup; + + +static int +init_constants(void) +{ + amp = ((PyUnicodeObject*)PyUnicode_DecodeASCII(samp, 5, NULL))->str; + lt = ((PyUnicodeObject*)PyUnicode_DecodeASCII(slt, 4, NULL))->str; + gt = ((PyUnicodeObject*)PyUnicode_DecodeASCII(sgt, 4, NULL))->str; + qt = ((PyUnicodeObject*)PyUnicode_DecodeASCII(sqt, 6, NULL))->str; + + PyObject *module = PyImport_ImportModule("jinja2.utils"); + if (!module) + return 0; + markup = PyObject_GetAttrString(module, "Markup"); + Py_DECREF(module); + + return 1; +} + +static PyObject* +escape_unicode(PyUnicodeObject *in) +{ + PyUnicodeObject *out; + Py_UNICODE *outp; + + /* First we need to figure out how long the escaped string will be */ + int len = 0, erepl = 0, repl = 0; + Py_UNICODE *inp = in->str; + while (*(inp) || in->length > inp - in->str) + switch (*inp++) { + case '&': + len += 5; + erepl++; + break; + case '"': + len += 6; + erepl++; + break; + case '<': + case '>': + len += 4; + erepl++; + break; + default: + len++; + } + + /* Do we need to escape anything at all? */ + if (!erepl) { + Py_INCREF(in); + return (PyObject*)in; + } + + out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, len); + if (!out) + return NULL; + + outp = out->str; + inp = in->str; + while (*(inp) || in->length > inp - in->str) { + /* copy rest of string if we have replaced everything */ + if (repl == erepl) { + Py_UNICODE_COPY(outp, inp, in->length - (inp - in->str)); + break; + } + /* regular replacements */ + switch (*inp) { + case '&': + Py_UNICODE_COPY(outp, amp, 5); + outp += 5; + repl++; + break; + case '"': + Py_UNICODE_COPY(outp, qt, 6); + outp += 6; + repl++; + break; + case '<': + Py_UNICODE_COPY(outp, lt, 4); + outp += 4; + repl++; + break; + case '>': + Py_UNICODE_COPY(outp, gt, 4); + outp += 4; + repl++; + break; + default: + *outp++ = *inp; + }; + ++inp; + } + + return (PyObject*)out; +} + + +static PyObject* +escape(PyObject *self, PyObject *args) +{ + PyObject *text = NULL, *s = NULL, *rv = NULL; + if (!PyArg_UnpackTuple(args, "escape", 1, 2, &text)) + return NULL; + + /* we don't have to escape integers, bools or floats */ + if (PyInt_CheckExact(text) || PyLong_CheckExact(text) || + PyFloat_CheckExact(text) || PyBool_Check(text) || + text == Py_None) { + args = PyTuple_New(1); + if (!args) { + Py_DECREF(s); + return NULL; + } + PyTuple_SET_ITEM(args, 0, text); + return PyObject_CallObject(markup, args); + } + + /* if the object has an __html__ method that performs the escaping */ + PyObject *html = PyObject_GetAttrString(text, "__html__"); + if (html) { + rv = PyObject_CallObject(html, NULL); + Py_DECREF(html); + return rv; + } + + /* otherwise make the object unicode if it isn't, then escape */ + PyErr_Clear(); + if (!PyUnicode_Check(text)) { + PyObject *unicode = PyObject_Unicode(text); + if (!unicode) + return NULL; + s = escape_unicode((PyUnicodeObject*)unicode); + Py_DECREF(unicode); + } + else + s = escape_unicode((PyUnicodeObject*)text); + + /* convert the unicode string into a markup object. */ + args = PyTuple_New(1); + if (!args) { + Py_DECREF(s); + return NULL; + } + PyTuple_SET_ITEM(args, 0, (PyObject*)s); + rv = PyObject_CallObject(markup, args); + Py_DECREF(args); + return rv; +} + + +static PyObject * +tb_set_next(PyObject *self, PyObject *args) +{ + PyTracebackObject *tb, *old; + PyObject *next; + + if (!PyArg_ParseTuple(args, "O!O:tb_set_next", &PyTraceBack_Type, &tb, &next)) + return NULL; + if (next == Py_None) + next = NULL; + else if (!PyTraceBack_Check(next)) { + PyErr_SetString(PyExc_TypeError, + "tb_set_next arg 2 must be traceback or None"); + return NULL; + } + else + Py_INCREF(next); + + old = tb->tb_next; + tb->tb_next = (PyTracebackObject*)next; + Py_XDECREF(old); + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyMethodDef module_methods[] = { + {"escape", (PyCFunction)escape, METH_VARARGS, + "escape(s) -> string\n\n" + "Convert the characters &, <, >, and \" in string s to HTML-safe\n" + "sequences. Use this if you need to display text that might contain\n" + "such characters in HTML."}, + {"tb_set_next", (PyCFunction)tb_set_next, METH_VARARGS, + "Set the tb_next member of a traceback object."}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC +init_speedups(void) +{ + if (!init_constants()) + return; + + Py_InitModule3("jinja2._speedups", module_methods, ""); +} diff --git a/jinja2/debug.py b/jinja2/debug.py index 1b558f0..75d70b2 100644 --- a/jinja2/debug.py +++ b/jinja2/debug.py @@ -134,9 +134,12 @@ def _init_ugly_crap(): return tb_set_next -# no ctypes, no fun +# try to get a tb_set_next implementation try: - tb_set_next = _init_ugly_crap() -except: - tb_set_next = None + from jinja2._speedups import tb_set_next +except ImportError: + try: + tb_set_next = _init_ugly_crap() + except: + tb_set_next = None del _init_ugly_crap diff --git a/jinja2/utils.py b/jinja2/utils.py index c030d24..5e6b403 100644 --- a/jinja2/utils.py +++ b/jinja2/utils.py @@ -16,20 +16,6 @@ from functools import update_wrapper from itertools import imap -def escape(obj, attribute=False): - """HTML escape an object.""" - if obj is None: - return u'' - elif hasattr(obj, '__html__'): - return obj.__html__() - return Markup(unicode(obj) - .replace('&', '&') - .replace('>', '>') - .replace('<', '<') - .replace('"', '"') - ) - - def soft_unicode(s): """Make a string unicode if it isn't already. That way a markup string is not converted back to unicode. @@ -317,3 +303,23 @@ class LRUCache(object): rv._mapping = deepcopy(self._mapping) rv._queue = deepcopy(self._queue) return rv + + +# we have to import it down here as the speedups module imports the +# markup type which is define above. +try: + from jinja2._speedups import escape +except ImportError: + def escape(obj): + """Convert the characters &, <, >, and " in string s to HTML-safe + sequences. Use this if you need to display text that might contain + such characters in HTML. + """ + if hasattr(obj, '__html__'): + return obj.__html__() + return Markup(unicode(obj) + .replace('&', '&') + .replace('>', '>') + .replace('<', '<') + .replace('"', '"') + ) diff --git a/setup.py b/setup.py index 23be036..a292d5b 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,9 @@ import sys import ez_setup ez_setup.use_setuptools() -from setuptools import setup +from setuptools import setup, Extension, Feature +from distutils.command.build_ext import build_ext +from distutils.errors import CCompilerError, DistutilsPlatformError def list_files(path): @@ -69,6 +71,29 @@ def list_files(path): yield fn +class optional_build_ext(build_ext): + """This class allows C extension building to fail.""" + + def run(self): + try: + build_ext.run(self) + except DistutilsPlatformError: + self._unavailable() + + def build_extension(self, ext): + try: + build_ext.build_extension(self, ext) + except CCompilerError, x: + self._unavailable() + + def _unavailable(self): + print '*' * 70 + print """WARNING: +An optional C extension could not be compiled, speedups will not be +available.""" + print '*' * 70 + + setup( name='Jinja 2', version='2.0dev', @@ -78,7 +103,7 @@ setup( author_email='armin.ronacher@active-4.com', description='A small but fast and easy to use stand-alone template ' 'engine written in pure python.', - long_description = __doc__, + long_description=__doc__, # jinja is egg safe. But because we distribute the documentation # in form of html and txt files it's a better idea to extract the files zip_safe=False, @@ -98,5 +123,13 @@ setup( ('docs/html', list(list_files('docs/html'))), ('docs/txt', list(list_files('docs/src'))) ], + features={ + 'speedups': Feature("optional C speed-enhancements", + standard=True, + ext_modules=[ + Extension('jinja2._speedups', ['jinja2/_speedups.c']) + ] + ) + }, extras_require={'plugin': ['setuptools>=0.6a2']} ) -- 2.26.2