summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorianb <devnull@localhost>2007-07-24 00:30:47 +0000
committerianb <devnull@localhost>2007-07-24 00:30:47 +0000
commit5e4933d8e0c9c5424872ed789ad999229c00bbb0 (patch)
treec62c74d21c3b40183beeabc40f6a97051637f3b0
parent73e753d1ef09c8393f92874c458574e7af0df25b (diff)
downloadtempita-5e4933d8e0c9c5424872ed789ad999229c00bbb0.tar.gz
Handle unicode/str more nicely/properly. Better error if you pass a non-dict in
-rw-r--r--docs/index.txt182
-rw-r--r--tempita/__init__.py36
2 files changed, 209 insertions, 9 deletions
diff --git a/docs/index.txt b/docs/index.txt
index def94a8..f19e8d5 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -1,6 +1,8 @@
Tempita
+++++++
+.. contents::
+
:author: Ian Bicking <ianb@colorstudy.com>
Status & License
@@ -13,3 +15,183 @@ seek to take over the templating world, or adopt many new features.
I just wanted a small templating language for cases when ``%`` and
``string.Template`` weren't enough.
+Why Another Templating Language
+===============================
+
+Surely the world has enough templating languages? So why did I write
+another.
+
+I initially used `Cheetah <http://cheetahtemplate.org/>`_ as the
+templating language for `Paste Script
+<http://pythonpaste.org/script/>`_, but this caused quite a few
+problems. People frequently had problems installing Cheetah because
+it includes a C extension. Also, the errors and invocation can be a
+little confusing. This might be okay for something that used
+Cheetah's features extensively, except that the templating was a very
+minor feature of the system, and many people didn't even understand or
+care about where templating came into the system.
+
+At the same time, I was starting to create reusable WSGI components
+that had some templating in them. Not a lot of templating, but enough
+that ``string.Template`` had become too complicated -- I need if
+statements and loops.
+
+Given this, I started looking around for a very small templating
+language, and I didn't like anything I found. Many of them seemed
+awkward or like toys that were more about the novelty of the
+implementation than the utility of the language.
+
+So one night when I felt like coding but didn't feel like working on
+anything I was already working on, I wrote this. It was first called
+``paste.util.template``, but I decided it deserved a life of its own,
+hence Tempita.
+
+The Interface
+=============
+
+The interface is intended to look a lot like ``string.Template``. You
+can create a template object like::
+
+ >>> import tempita
+ >>> tmpl = tempita.Template("""Hello {{name}}""")
+ >>> tmpl.substitute(name='Bob')
+ 'Hello Bob'
+
+Or if you want to skip the class::
+
+ >>> tempita.sub("Hello {{name}}", name='Alice')
+ 'Hello Alice'
+
+Note that the language allows arbitrary Python to be executed, so
+your templates must be trusted.
+
+You can give a name to your template, which is handy when there is an
+error (the name will be displayed)::
+
+ >>> tmpl = tempita.Template('Hi {{name}}', name='tmpl')
+ >>> tmpl.substitute()
+ Traceback (most recent call last):
+ ...
+ NameError: name 'name' is not defined at line 1 column 6 in file tmpl
+
+You can also give a namespace to use by default, which
+``.substitute(...)`` will augment::
+
+ >>> tmpl = tempita.Template(
+ ... 'Hi {{upper(name)}}',
+ ... namespace=dict(upper=lambda s: s.upper()))
+ >>> tmpl.substitute(name='Joe')
+ 'Hi JOE'
+
+Lastly, you can give a dictionary-like object as the argument to
+``.substitute``, like::
+
+ >>> name = 'Jane'
+ >>> tmpl.substitute(locals())
+ 'Hi JANE'
+
+There's also an `HTMLTemplate`_ class that is more appropriate for
+templates that produce HTML.
+
+Unicode
+-------
+
+Tempita tries to handle unicode gracefully, for some value of
+"graceful". ``Template`` objects have a ``default_encoding``
+attribute. It will try to use that encoding whenever ``unicode`` and
+``str`` objects are mixed in the template. E.g.::
+
+ >>> tmpl = tempita.Template(u'Hi {{name}}')
+ >>> tmpl.substitute(name='Jos\xc3\xa9')
+ u'Hi Jos\xe9'
+ >>> tmpl = tempita.Template('Hi {{name}}')
+ >>> tmpl.substitute(name=u'Jos\xe9')
+ 'Hi Jos\xc3\xa9'
+
+The Language
+============
+
+The language is fairly simple; all the constructs look like
+``{{stuff}}``.
+
+To insert a variable or expression, use ``{{expression}}``. You can't
+use ``}}`` in your expression, but if it comes up just use ``} }``
+(put a space between them). You can pass your expression through
+*filters* with ``{{expression | filter}}``, for instance
+``{{expression | repr}}``. This is entirely equivalent to
+``{{repr(expression)}}``. But it might look nicer to some people; I
+took it from Django because I liked it. There's a shared namespace,
+so ``repr`` is just an object in the namespace.
+
+If you want to have ``{{`` or ``}}`` in your template, you must use
+the built-in variables like ``{{start_braces}}`` and
+``{{end_braces}}``. There's no escape character.
+
+You can do an if statement with::
+
+ {{if condition}}
+ true stuff
+ {{elif other_condition}}
+ other stuff
+ {{else}}
+ final stuff
+ {{endif}}
+
+Some of the blank lines will be removed when, as in this case, they
+only contain a single directive. A trailing ``:`` is optional.
+
+Loops should be unsurprising::
+
+ {{for a, b in items}}
+ {{a}} = {{b | repr}}
+ {{endfor}}
+
+For anything more complicated, you can use blocks of Python code,
+like::
+
+ {{py:x = 1}}
+
+ {{py:
+ lots of code
+ }}
+
+The first form allows statements, like an assignment or raising an
+exception. The second form is for multiple lines. If you have
+multiple lines, then ``{{py:`` must be on a line of its own and the
+code can't be indented (except for normal indenting in ``def x():``
+etc).
+
+These can't output any values, but they can calculate values and
+define functions. So you can do something like::
+
+ {{py:
+ def pad(s):
+ return s + ' '*(20-len(s))
+ }}
+ {{for name, value in kw.items()}}
+ {{s | pad}} {{value | repr}}
+ {{endfor}}
+
+The last construct is for setting defaults in your template, like::
+
+ {{default width = 100}}
+
+You can use this so that the ``width`` variable will always have a
+value in your template (the number ``100``). If someone calls
+``tmpl.substitute(width=200)`` then this will have no effect; only if
+the variable is undefined will this default matter.
+
+As a last detail ``{{# comments...}}`` doesn't do anything at all,
+because it is a comment.
+
+Still To Do
+===========
+
+Currently nested structures in ``for`` loop assignments don't work,
+like ``for (a, b), c in x``. They should.
+
+There's no way to handle exceptions, except in your ``py:`` code. I'm
+not sure what there should be.
+
+Probably I should try to dedent ``py:`` code.
+
diff --git a/tempita/__init__.py b/tempita/__init__.py
index 1e42b5a..7f24a70 100644
--- a/tempita/__init__.py
+++ b/tempita/__init__.py
@@ -1,10 +1,9 @@
"""
A small templating language
-This implements a small templating language for use internally in
-Paste and Paste Script. This language implements if/elif/else,
-for/continue/break, expressions, and blocks of Python code. The
-syntax is::
+This implements a small templating language. This language implements
+if/elif/else, for/continue/break, expressions, and blocks of Python
+code. The syntax is::
{{any expression (function calls etc)}}
{{any expression | filter}}
@@ -106,7 +105,11 @@ class Template(object):
"You can only give positional *or* keyword arguments")
if len(args) > 1:
raise TypeError(
- "You can only give on positional argument")
+ "You can only give one positional argument")
+ if not hasattr(args[0], 'items'):
+ raise TypeError(
+ "If you pass in a single argument, you must pass in a dictionary-like object (with a .items() method); you gave %r"
+ % (args[0],))
kw = args[0]
ns = self.default_namespace.copy()
ns.update(self.namespace)
@@ -230,7 +233,14 @@ class Template(object):
except UnicodeDecodeError:
value = str(value)
else:
- value = str(value)
+ if not isinstance(value, basestring):
+ if hasattr(value, '__unicode__'):
+ value = unicode(value)
+ else:
+ value = str(value)
+ if (isinstance(value, unicode)
+ and self.default_encoding):
+ value = value.encode(self.default_encoding)
except:
exc_info = sys.exc_info()
e = exc_info[1]
@@ -238,13 +248,21 @@ class Template(object):
raise exc_info[0], e, exc_info[2]
else:
if self._unicode and isinstance(value, str):
- if not self.decode_encoding:
+ if not self.default_encoding:
raise UnicodeDecodeError(
'Cannot decode str value %r into unicode '
'(no default_encoding provided)' % value)
- value = value.decode(self.default_encoding)
+ try:
+ value = value.decode(self.default_encoding)
+ except UnicodeDecodeError, e:
+ raise UnicodeDecodeError(
+ e.encoding,
+ e.object,
+ e.start,
+ e.end,
+ e.reason + ' in string %r' % value)
elif not self._unicode and isinstance(value, unicode):
- if not self.decode_encoding:
+ if not self.default_encoding:
raise UnicodeEncodeError(
'Cannot encode unicode value %r into str '
'(no default_encoding provided)' % value)