changeset 713:5420fe9d99a9 trunk

The `Markup` class now supports mappings for right hand of the `%` (modulo) operator in the same way the Python string classes do, except that the substituted values are escape. Also, the special constructor which took positional arguments that would be substituted was removed. Thus the `Markup` class now supports the same arguments as that of its `unicode` base class. Closes #211. Many thanks to Christian Boos for the patch!
author cmlenz
date Tue, 08 Apr 2008 18:18:18 +0000
parents 0af8f63514b8
children fc6d9d2a3527
files ChangeLog doc/upgrade.txt genshi/_speedups.c genshi/core.py genshi/output.py genshi/tests/core.py
diffstat 6 files changed, 102 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -71,6 +71,12 @@
    the `<py:match>` directive.
  * Improve error reporting when accessing an attribute in a Python expression
    raises an `AttributeError` (ticket #191).
+ * The `Markup` class now supports mappings for right hand of the `%` (modulo)
+   operator in the same way the Python string classes do, except that the
+   substituted values are escape. Also, the special constructor which took
+   positional arguments that would be substituted was removed. Thus the
+   `Markup` class now supports the same arguments as that of its `unicode`
+   base class (ticket #211).
 
 
 Version 0.4.4
--- a/doc/upgrade.txt
+++ b/doc/upgrade.txt
@@ -1,3 +1,4 @@
+================
 Upgrading Genshi
 ================
 
@@ -7,19 +8,12 @@
 .. sectnum::
 
 
+------------------------------------
 Upgrading from Genshi 0.4.x to 0.5.x
 ------------------------------------
 
-Genshi 0.5 introduces a new, alternative syntax for text templates,
-which is more flexible and powerful compared to the old syntax. For
-backwards compatibility, this new syntax is not used by default,
-though it will be in a future version. It is recommended that you
-migrate to using this new syntax. To do so, simply rename any
-references in your code to ``TextTemplate`` to ``NewTextTemplate``. To
-explicitly use the old syntax, use ``OldTextTemplate`` instead, so
-that you can be sure you'll be using the same language when the
-default in Genshi is changed (at least until the old implementation is
-completely removed).
+Error Handling
+--------------
 
 The default error handling mode has been changed to "strict". This
 means that accessing variables not defined in the template data will
@@ -30,6 +24,9 @@
 warned that lenient error handling may be removed completely in a
 future release.
 
+Match Template Processing
+-------------------------
+
 There has also been a subtle change to how ``py:match`` templates are
 processed: in previous versions, all match templates would be applied
 to the content generated by the matching template, and only the
@@ -42,7 +39,39 @@
 have any effect on most applications, but you may want to check your
 use of match templates to make sure.
 
+Text Templates
+--------------
 
+Genshi 0.5 introduces a new, alternative syntax for text templates,
+which is more flexible and powerful compared to the old syntax. For
+backwards compatibility, this new syntax is not used by default,
+though it will be in a future version. It is recommended that you
+migrate to using this new syntax. To do so, simply rename any
+references in your code to ``TextTemplate`` to ``NewTextTemplate``. To
+explicitly use the old syntax, use ``OldTextTemplate`` instead, so
+that you can be sure you'll be using the same language when the
+default in Genshi is changed (at least until the old implementation is
+completely removed).
+
+``Markup`` Constructor
+----------------------
+
+The ``Markup`` class now longer has a specialized constructor. The old
+(undocumented) constructor provided a shorthand for doing positional
+substitutions. If you have code like this:
+
+.. code-block:: python
+
+  Markup('<b>%s</b>', name)
+
+You can simply replace it by the more explicit:
+
+.. code-block:: python
+
+  Markup('<b>%s</b>') % name
+
+
+------------------------------------
 Upgrading from Genshi 0.3.x to 0.4.x
 ------------------------------------
 
@@ -66,6 +95,7 @@
 information.
 
 
+---------------------
 Upgrading from Markup
 ---------------------
 
--- a/genshi/_speedups.c
+++ b/genshi/_speedups.c
@@ -144,46 +144,6 @@
     return ret;
 }
 
-static PyObject *
-Markup_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-    PyObject *self, *text, *tmp, *args2;
-    int nargs, i;
-
-    nargs = PyTuple_GET_SIZE(args);
-    if (nargs < 2) {
-        return PyUnicode_Type.tp_new(type, args, NULL);
-    }
-
-    text = PyTuple_GET_ITEM(args, 0);
-    args2 = PyTuple_New(nargs - 1);
-    if (args2 == NULL) {
-        return NULL;
-    }
-    for (i = 1; i < nargs; i++) {
-        tmp = escape(PyTuple_GET_ITEM(args, i), 1);
-        if (tmp == NULL) {
-            Py_DECREF(args2);
-            return NULL;
-        }
-        PyTuple_SET_ITEM(args2, i - 1, tmp);
-    }
-    tmp = PyUnicode_Format(text, args2);
-    Py_DECREF(args2);
-    if (tmp == NULL) {
-        return NULL;
-    }
-    args = PyTuple_New(1);
-    if (args == NULL) {
-        Py_DECREF(tmp);
-        return NULL;
-    }
-    PyTuple_SET_ITEM(args, 0, tmp);
-    self = PyUnicode_Type.tp_new(type, args, NULL);
-    Py_DECREF(args);
-    return self;
-}
-
 PyDoc_STRVAR(escape__doc__,
 "Create a Markup instance from a string and escape special characters\n\
 it may contain (<, >, & and \").\n\
@@ -325,9 +285,38 @@
 Markup_mod(PyObject *self, PyObject *args)
 {
     PyObject *tmp, *tmp2, *ret, *args2;
-    int i, nargs;
+    int i, nargs = 0;
+    PyObject *kwds = NULL;
 
-    if (PyTuple_Check(args)) {
+    if (PyDict_Check(args)) {
+        kwds = args;
+    }
+    if (kwds && PyDict_Size(kwds)) {
+        PyObject *kwcopy, *key, *value;
+        Py_ssize_t pos = 0;
+
+        kwcopy = PyDict_Copy( kwds );
+        if (kwcopy == NULL) {
+            return NULL;
+        }
+        while (PyDict_Next(kwcopy, &pos, &key, &value)) {
+            tmp = escape(value, 1);
+            if (tmp == NULL) {
+                Py_DECREF(kwcopy);
+                return NULL;
+            }
+            if (PyDict_SetItem(kwcopy, key, tmp) < 0) {
+                Py_DECREF(tmp);
+                Py_DECREF(kwcopy);
+                return NULL;
+            }
+        }
+        tmp = PyUnicode_Format(self, kwcopy);
+        Py_DECREF(kwcopy);
+        if (tmp == NULL) {
+            return NULL;
+        }
+    } else if (PyTuple_Check(args)) {
         nargs = PyTuple_GET_SIZE(args);
         args2 = PyTuple_New(nargs);
         if (args2 == NULL) {
@@ -589,7 +578,7 @@
     
     0,          /*tp_init*/
     0,          /*tp_alloc  will be set to PyType_GenericAlloc in module init*/
-    Markup_new, /*tp_new*/
+    0,          /*tp_new*/
     0,          /*tp_free  Low-level free-memory routine */
     0,          /*tp_is_gc For PyObject_IS_GC */
     0,          /*tp_bases*/
--- a/genshi/core.py
+++ b/genshi/core.py
@@ -419,11 +419,6 @@
     """
     __slots__ = []
 
-    def __new__(cls, text='', *args):
-        if args:
-            text %= tuple(map(escape, args))
-        return unicode.__new__(cls, text)
-
     def __add__(self, other):
         return Markup(unicode(self) + unicode(escape(other)))
 
@@ -431,9 +426,13 @@
         return Markup(unicode(escape(other)) + unicode(self))
 
     def __mod__(self, args):
-        if not isinstance(args, (list, tuple)):
-            args = [args]
-        return Markup(unicode.__mod__(self, tuple(map(escape, args))))
+        if isinstance(args, dict):
+            args = dict(zip(args.keys(), map(escape, args.values())))
+        elif isinstance(args, (list, tuple)):
+            args = tuple(map(escape, args))
+        else:
+            args = escape(args)
+        return Markup(unicode.__mod__(self, args))
 
     def __mul__(self, num):
         return Markup(unicode(self) * num)
--- a/genshi/output.py
+++ b/genshi/output.py
@@ -244,7 +244,7 @@
                 if sysid:
                     buf.append(' "%s"')
                 buf.append('>\n')
-                yield Markup(u''.join(buf), *filter(None, data))
+                yield Markup(u''.join(buf)) % filter(None, data)
                 have_doctype = True
 
             elif kind is START_CDATA:
@@ -343,7 +343,7 @@
                 if sysid:
                     buf.append(' "%s"')
                 buf.append('>\n')
-                yield Markup(u''.join(buf), *filter(None, data))
+                yield Markup(u''.join(buf)) % filter(None, data)
                 have_doctype = True
 
             elif kind is START_CDATA:
@@ -446,7 +446,7 @@
                 if sysid:
                     buf.append(' "%s"')
                 buf.append('>\n')
-                yield Markup(u''.join(buf), *filter(None, data))
+                yield Markup(u''.join(buf)) % filter(None, data)
                 have_doctype = True
 
             elif kind is PI:
--- a/genshi/tests/core.py
+++ b/genshi/tests/core.py
@@ -62,6 +62,10 @@
 
 class MarkupTestCase(unittest.TestCase):
 
+    def test_new_with_encoding(self):
+        markup = Markup('Döner', encoding='utf-8')
+        self.assertEquals("<Markup u'D\\xf6ner'>", repr(markup))
+
     def test_repr(self):
         markup = Markup('foo')
         self.assertEquals("<Markup u'foo'>", repr(markup))
@@ -107,6 +111,16 @@
         assert type(markup) is Markup
         self.assertEquals('<b>&amp;</b> boo', markup)
 
+    def test_mod_mapping(self):
+        markup = Markup('<b>%(foo)s</b>') % {'foo': '&'}
+        assert type(markup) is Markup
+        self.assertEquals('<b>&amp;</b>', markup)
+
+    def test_mod_noescape(self):
+        markup = Markup('<b>%(amp)s</b>') % {'amp': Markup('&amp;')}
+        assert type(markup) is Markup
+        self.assertEquals('<b>&amp;</b>', markup)
+
     def test_mul(self):
         markup = Markup('<b>foo</b>') * 2
         assert type(markup) is Markup
Copyright (C) 2012-2017 Edgewall Software