Permalink
Browse files

added C escape and tb_set_next functions

--HG--
branch : trunk
  • Loading branch information...
1 parent 284f200 commit bd33f1170e3241e2b5e061e44ec05b8d175e5739 @mitsuhiko mitsuhiko committed Apr 18, 2008
Showing with 278 additions and 20 deletions.
  1. +216 −0 jinja2/_speedups.c
  2. +7 −4 jinja2/debug.py
  3. +20 −14 jinja2/utils.py
  4. +35 −2 setup.py
View
@@ -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 <Python.h>
+
+
+static char *samp = "&amp;", *slt = "&lt;", *sgt = "&gt;", *sqt = "&quot;";
+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, "");
+}
View
@@ -134,9 +134,12 @@ def tb_set_next(tb, next):
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
View
@@ -16,20 +16,6 @@
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('&', '&amp;')
- .replace('>', '&gt;')
- .replace('<', '&lt;')
- .replace('"', '&quot;')
- )
-
-
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 @@ def __deepcopy__(self):
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('&', '&amp;')
+ .replace('>', '&gt;')
+ .replace('<', '&lt;')
+ .replace('"', '&quot;')
+ )
View
@@ -57,7 +57,9 @@
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 @@ def list_files(path):
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 @@ def list_files(path):
('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']}
)

0 comments on commit bd33f11

Please sign in to comment.