added C escape and tb_set_next functions
authorArmin Ronacher <armin.ronacher@active-4.com>
Fri, 18 Apr 2008 07:17:32 +0000 (09:17 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Fri, 18 Apr 2008 07:17:32 +0000 (09:17 +0200)
--HG--
branch : trunk

jinja2/_speedups.c [new file with mode: 0644]
jinja2/debug.py
jinja2/utils.py
setup.py

diff --git a/jinja2/_speedups.c b/jinja2/_speedups.c
new file mode 100644 (file)
index 0000000..30bea54
--- /dev/null
@@ -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, "");
+}
index 1b558f04be66933a46804de43928b27cd8b7ede8..75d70b2d3043c58c1c460b11d3f5e5ed6f4a42b7 100644 (file)
@@ -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
index c030d24c35795f8232a33e224115a5cc4fe95418..5e6b4036bd5f4f5f5e2dc0089e606134f7011749 100644 (file)
@@ -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('&', '&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 @@ 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('&', '&amp;')
+            .replace('>', '&gt;')
+            .replace('<', '&lt;')
+            .replace('"', '&quot;')
+        )
index 23be036eaf50990726c7e525d899dea20865747b..a292d5bcec7f7ea97b1dfb888d114c19fcb2b3e3 100644 (file)
--- 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']}
 )