See also:
* ReviewBoardFeedback_
Cheetah: The Python-Powered Template Engine
===========================================
*By Tavis Rudd, Mike Orr and Ian Bicking*
Affiliations
------------
Tavis Rudd is a freelance consultant. Mike Orr does web application
development at a publishing company. Ian Bicking is a freelance web
developer.
Abstract
--------
Cheetah is a Python-powered template engine and code generator. It
has many potential uses -- generating SQL, SGML, LaTeX_, PostScript_,
form emails, etc. -- but its principle use is for web developers as an
alternative to ASP, JSP, PHP and PSP. Cheetah integrates tightly with
http://webware.sourceforge.netWebware for Python, but is also well
suited for use as a standalone tool. The current version is available
at http://www.cheetahtemplate.org/, along with a Users' Guide.
Cheetah is distributed under a BSD-like license.
This paper introduces Cheetah and examines its influences, design
principles, and implementation. It also highlights the unique features
that distinguish it from other template engines.
Keywords
--------
templates, code generators, web development, Webware for Python,
servlets, parsers
Introduction and Design Influences
----------------------------------
Cheetah is a template engine and code generator that grew out of the
'Webware for Python' project [1]. In April 2001, Webware developers
could write servlets using only pure Python code or Python Server
Pages (PSP) [2]. Neither facilitated the rapid creation of the large
HTML templates for dynamic web applications. Vanilla Python is not
tailored for extended string interpolation, nor should it be. PSP is
well suited for throwing together a quick page or two, but tends to
degrade into a tangled mess of spaghetti code when used in larger
projects. It's also more verbose than necessary and is hard to
explain to non-programmers.
Several Webware developers decided to address this situation and
started a discussion about porting or developing a template system
that made it easy to access any Python data structure and manage
control flow from within text documents. As we were fleeing spaghetti
code, we felt that being able to separate the interface from the
application code was essential. It should work well with any
text-based output, not just HTML. Builtin output caching would be
nice. In the name of maintainability, we wanted to be able to inherit
and extend one template from another. Most importantly, the basic
syntax and concepts needed to be simple and consistent enough for
non-programmers to understand, while being flexible enough to give
programmers the full expressive power of Python. Our wishlist was
long!
Over the following months, we closely examined many template systems
with radically different approaches: Zope's DTML and Zope Page
Templates [3], Alex Martelli's Yet Another Python Templating Utility
(YAPTU) [4], Ka-ping Yee's string interpolation module (itpl) [5],
Quixote's Python Template Language [6], Velocity [7] and WebMacro_ [8]
from Java, Smarty [9] and PHPLib's Template class [10] from PHP, and
the Template Toolkit from Perl [11]. Much later, we also looked at
Skunkweb's STML [12].
We were tempted by Zope Page Templates (ZPT) because it seemed to work
well for teams that had designers working with WYSIWYG tools. However,
we decided against a ZPT-like design for several reasons. First, we
wanted a generalized system that worked with any form of text output,
not just HTML. A recent discussion on Advogato [13] highlighted our
other concerns:
[ZPT] seems like a good idea, but it has a number of serious
problems. The biggest problem is that it assumes that the
static structure of a web page is in any way similar to how
that web page is going to look when displayed
dynamically. However, most large web sites use the notion of
"components" - that is, re-usable fragments of dynamic HTML
which are assembled to form a complete page. ...
What I have experienced is that HTML pages for
industrial-strength web sites like Amazon's is [sic] made up
of a myriad of dynamically generated elements - there is very
little static structure to the page at all. Thus, it is only
useful to preview the dynamic elements. Previewing the static
structure of the page is not in the least bit helpful, and
merely serves to make the artists' job harder by distracting
them with "dummy" content that makes the template more
verbose. ...
The WYSIWYG paradigm I believe involves two basic
assumptions:
* That the developer of a piece of content is able to
see, in real- time, the visual effects of a particular
design modification. That the operations on the
document are all fundamentally visual operations, that
is, operations on appearance, layout and style.
* In other words, I think that in order to be truly
WYSIWYG, the content developers need not concern
themselves with the abstractions that lie behind the
visual presentation. They operate directly on the
visual presentation and never need to penetrate beneath
it.
However, in a highly dynamic system, it really isn't possible
to visually present any more than a snapshot of the dynamic
system at any given moment. In a highly dynamic system, you
really do want to expose to the designer the abstract
mechanisms behind the facade - because those abstract
elements are what defines the dynamics. It means that the
designer has to think abstractly, and must operate on that
level instead of the level of superficial appearances. ----
Talin_ (quoted with permission)
.. _Talin: http://www.advogato.org/person/Talin/
----------------------------------------
Code kept in HTML attributes is largely inaccessible to the
user -- they can't see it, they won't know if they've deleted
it, and they'll have a really hard time putting it in. [The
designers] are capable of doing some programming -- the most
simple and most common case is simply indicating where
certain values should go (like username, status message,
etc). In fact, one of ZPT's big detractions, in my mind, is
just how difficult it is just to do that simple insertion
(username, if I remember
correctly).
One of the great things about WYSIWYG editors is that they
are entirely concrete. Now it's not possible to make a
dynamic page entirely concrete, but it is nice to at least
make the logic visible.
And, of course, ZPT is connected to HTML and XML. But just
about every significant application will also have to produce
CSS, Javascript, and email messages. Maybe this is part of
the transition you've already made from XML to HTML, and
eventually to plain text. It's a significant problem, but a
solvable one. In the same way, by putting in a
non-HTML-attribute syntax, you can potentially make ZPT more
concrete. I don't want to seem too negative -- there are some
really good parts to ZPT, particularly when changing IMG SRC
value, which will piss off the WYSIWYG editor badly in most
other systems. ---- `Ian Bicking`_
Furthermore, ZPT assumes a clear division of roles between the
programmer and the designer. We preferred a system with a generalized
core that could adapt to uses and work flow patterns that weren't
anticipated a priori. Our conclusion was that if we ever needed the
WYSIWYG abilities of ZPT, we'd build an extra layer to provide it.
None of the existing systems was a perfect match, so we began work on
several competing modules that blended what we considered the best
features in the existing systems. Cheetah's predecessor,
``TemplateServer_``, was one of these modules. It began as a
simple regex-based tool, vaguely resembling YAPTU. As we examined the
syntax and design philosophies used in other systems, and as its
author slowly learned how to build a parser, TemplateServer_ evolved
and gradually gained general support among the Webware developers. In
June 2001, we retired the name ``TemplateServer_`` in favor
of ``Cheetah``.
Early on, we abandoned the pointy-bracket syntax of PSP and other many
other systems because the tags are invisible in browsers when
something goes wrong, are too verbose, and in general work poorly with
GUI tools. Instead, we adopted the basic syntax of Velocity: ``$``'s
for interpolation placeholders and ``#``'s for directives.
Velocity also strongly influenced our perspective on syntax and
complexity. Initially, several of us argued that Cheetah's syntax
should be purposefully restricted, in order to prevent developers from
doing complex processing in Cheetah. We'd seen the mess that DTML had
become, so our maxim was "Cheetah for the simple tasks, Python for the
complex". It soon became clear that this stance was unnecessary and
would artificially limit Cheetah's usefulness. DTML had failed
because of its confusing, inconsistent syntax and the many hoops one
had to jump through to access plain Python, not because it had given
template writers powerful constructs to work with. Velocity, on the
other hand, was succeeding because it had a coherent syntax that was
expressive enough to handle most aspects of an application's
*interface* without requiring constant switches back to Java.
Cheetah users were complaining about being forced back into Python to
handle complex tasks that were still related to the
*interface*, so we decided to make most of Python's language
constructs directly accessible from Cheetah. Where Python has ``for
...``, Cheetah has ``#for ...``, and so on.
As a result, Cheetah's syntax became larger, but not necessarily more
complex. The original core is still easy for non-programmers to learn
and use. And, as most Cheetah language constructs are exact mirrors
of Python contructs, prefaced with a ``#`` or ``$``, someone with
Python experience won't have much re-learning to do. Cheetah's
object-orientation and error handling are semantically identical to
Python's. See the *Syntax Overview* section below for more
information.
Cheetah's original implementation translated templates into function
definitions, evaluated them and then dynamically bound the resulting
function as a method of a ``Template`` object, much like the cached
subroutines used by Perl's Template Toolkit. This approach was flawed.
Subtle ``tricks`` were needed for the inheritance features to work and
we were constantly running into conceptual brick walls and
ambiguities. Templates could be extended and reused, but via our own
black magic system rather than via Python's standard inheritance
structures. It also led to high initialization costs and painful
debugging. Webware's implementation of PSP gave us the idea to
compile templates into pure Python classes / modules. Most of
Cheetah's early problems were resolved by this decision and most of
its current strengths and unique features are a direct result of it.
Syntax Overview
---------------
Cheetah has two types of tags: placeholders and
directives. Placeholder tags begin with a dollar sign (``$varName``)
and are similar to data fields in a form letter or the %(key)s fields
Python's % operator uses. An alternate syntax (``${varName}``) may be
used to prevent ambiguity about where the placeholder ends. Directive
tags begin with a hash character (#) and are used for comments, loops,
conditional blocks, includes, and all other features.
Inside directive tags, Cheetah uses a Python-like syntax and
understands any valid Python expression. The main differences from
pure Python syntax are 1) all variable names are prefaced with a
dollar sign ($), 2) colons (:) are not used to mark the beginning of a
code block, and 3) indentation is not significant. Instead of using
indentation, Cheetah uses ``#end [if | for | try | etc.]``. Most of
the directive tags are direct mirrors of Python statements.
Cheetah also supports two PSP style tags as escapes to pure Python
code. These are not part of Cheetah's core syntax, but are included to
facilitate migration from PSP-style markup languages to Cheetah.
These tags may not be used inside of other Cheetah tags, and vice
versa.
* evalute expression and print the output: ``<%=`` ...``%>``
* execute code and discard output: ``<%`` ...``%>``
Cheetah's Language Constructs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following is a basic categorization of Cheetah's language
constructs. A detailed explanation is provided in the Users' Guide.
* Comments and documentation strings:
* ``## single line``
* ``#* multi line *#``
* Generation, caching and filtering of output:
* plain text
* output from simple expressions: ``$placeholders``
* output from more complex expressions: ``#echo``
* gobble the end of line: ``#slurp``
* parsed template file includes: ``#include``
* raw file includes: ``#include raw``
* verbatim output of Cheetah code: ``#raw`` ...``#end raw``
* cached placeholders: ``$*var``, ``$**var``
* cached regions: ``#cache`` ...``#cache``
* set the output filter: ``#filter`` ...
* Function/method calling without outputting the return value: ``#silent``
* Importing other modules and from other modules: ``#import``,
``#from``
* Inheritance:
* set the base classes to inherit from: ``#extends``
* set the name of the main method to implement: ``#implements``
* Compile-time declaration:
* define class attributes: ``#attr`` ...
* define class methods: ``#def`` ...``#end def``
* ``#block`` provides a simplified interface to ``#def``
* define class 'settings': ``#settings`` ...``#end settings`` using
the builtin SettingsManager API
* Run-time assignment:
* local vars: ``#set`` ...
* global vars: ``#set global`` ...
* Flow control:
* ``#if`` ...``#else`` ...``#else if`` (aka ``#elif``) ...``#end if``
* ``#for`` ...``#end for``
* ``#while`` ...``#end while``
* ``#break``
* ``#continue``
* ``#pass``
* ``#stop``
* error/exception handling:
* ``#assert``
* ``#raise``
* ``#try`` ...``#except`` ...``#else`` ... ``#end try`` and
``#finally``
* ``#errorCatcher``: a debugging tool that sets a default exception
catcher/handler for exceptions raised by $placeholder calls.
* Instructions to the parser/compiler:
* ``#breakpoint``
* ``#compiler-settings`` ...``#end compiler-settings``
How Cheetah Works
~~~~~~~~~~~~~~~~~
The heart of Cheetah is the ``Template`` class in the
``Cheetah.Template`` module. It serves two purposes. First, its
constructor method accepts a source string or file (filename or file
object) and compiles the source into a Python class. Second, it is
used as the base class for the generated classes. ``Template``
subclasses Webware's ``HTTPServlet`` class when it is available.
Thus, the generated classes can be used as Webware servlets or as
standalone utilities.
Generated template classes can either be used immediately or written
to a Python module file for future use. In the former case, the
methods and attributes of the generated class are dynamically added to
the instance of ``Template`` that did the compiling. They are
available as soon as compilation is complete. In the latter case,
Cheetah will wrap the generated class definition in some boilerplate
code to produce a complete module definition.
The file ``Example.tmpl`` contains the following source code::
#attr adj = "trivial"
This is a $adj example
This can processed using the python interactive interpreter::
>>> from Cheetah.Template import Template
>>> template = Template(file='Example.tmpl')
>>> print template
This is a trivial example
>>> print template # and again
This is a trivial example
>>>
>>> print template.generatedClassCode_()
# ... the source code of the generated Python class (see the Appendix for examples)
>>> print template.generatedModuleCode_()
# ... the source code of the generated Python module
Cheetah source files (``.tmpl``) can also be processed in a variety of
ways using the command line::
tavis@lucy: ~ > cheetah-compile -h
Cheetah 0.9.9b1 command-line compiler by Tavis Rudd and Ian Bicking
Compiles Cheetah files (.tmpl) into Webware servlet modules (.py)
Usage:
cheetah-compile [OPTIONS] FILES/DIRECTORIES
-R Recurse subdirectories
-p Print generated Python code to stdout
-w Write output of template to *.html
-v Be verbose
tavis@lucy: ~ > cheetah-compile -p Example.tmpl | python
This is a trivial example
tavis@lucy: ~ > ls Example.*
example.tmpl
tavis@lucy: ~ > cheetah-compile example.tmpl
tavis@lucy: ~ > ls Example.*
Example.tmpl Example.py
tavis@lucy: ~ > python Example.py
This is a trivial example
The Python modules that are generated from templates come with a
command-line interface themselves::
tavis@lucy: ~ > export adj=commandline
tavis@lucy: ~ > python Example.py --env
This is a commandline example
All of Cheetah's command line interfaces accept input via pipes and
can be chained together with other tools, as demonstrated by this
contrived example::
echo "My computer's name is $HOST" | cheetah-compile - | python - --env
In highly simplified pseudo-code, ``Example.py`` works something like
this::
from Cheetah.Template import Template
class Example(Template):
adj = "trivial"
def respond(self, trans=None, # trans is a Webware 'transaction' object
dummyTrans=False):
"""
This is the main method generated by Cheetah
"""
if not trans: # i.e. if run without Webware
trans = DummyTransaction_()
dummyTrans = True
write = trans.response().write # used to stream output
########################################
## START - generated method body
write('This is a ')
write(str(self.adj)) # generated from '$adj' at line, col (2, 11).
write(' example')
########################################
## END - generated method body
if dummyTrans:
return trans.response().getvalue()
else:
return ""
##################################################
## GENERATED ATTRIBUTES
adj = "trivial"
__str__ = respond
##################################################
## if run from command line:
if __name__ == '__main__':
Example().runAsrogram()
Webware servlets use the ``respond()`` method to respond to incoming
requests. It is also the core method of a Cheetah generated class.
If ``Example.py``, or any other Cheetah generated module, is placed in
a Webware servlet directory it will be loaded like any other Webware
servlet module.
For convenience, Cheetah makes the ``__str__`` method an alias to
``respond()``. Thus, ``print myTemplateObj_`` or ``print
Template(sourceString)`` will print the output of the template.
Getting Data Into Cheetah Templates
-----------------------------------
There are numerous options for getting data into Cheetah templates so
formatted output can be generated from it. These options are
identical to those available in normal Python: importing from modules,
querying databases, using mixin classes, etc. The Cheetah Users'
Guide contains examples.
What Makes Cheetah Unique?
--------------------------
Object-Oriented Documents
~~~~~~~~~~~~~~~~~~~~~~~~~
As described above, Cheetah templates are class definitions. Whereas
other template systems use the concept of ``blocks`` to define regions
of a template that can be overridden and customized, Cheetah uses
methods, attributes and inheritance:
#attr anAttrib = "An attribute of the template class"
#def myMethod1
the first method
#end def
#def myMethod2
the second method
#end def
Here's $anAttrib
Here's $myMethod1
Here's $myMethod2
Methods can have arguments::
#def myMethod($arg1='foo', $arg2='bar')
This is arg1: $arg1
This is arg2: $arg2
#end def
$myMethod(1,2)
Cheetah also provides the ``#block`` directive as a simple wrapper
around the ``#def`` directive. It allows a method to be defined and
used in place.
::
#block myMethod
This is a method defined and used in place.
#end block
is semantically identical to::
#def myMethod
This is a method defined and used in place.
#end def
$myMethod
NameMapper Variable Access
~~~~~~~~~~~~~~~~~~~~~~~~~~
One of our core aims with Cheetah was to make it easy for
non-programmers to use. To achieve this aim, we created a simplified
syntax for mapping variable names in Cheetah to values in Python. It's
known as the **NameMapper_ syntax** and makes it possible for
non-programmers to use Cheetah without knowing (a) what the difference
is between an object and a dictionary, (b) what functions and methods
are, and (c) what 'self' is. NameMapper_ syntax is used for all
variables in Cheetah placeholders and directives.
Consider this scenario:
You've been hired as a consultant to design and implement a customer
information system. You create a class that has a 'customers' method
that returns a dictionary of all the customer objects. Each customer
object has an 'address' method that returns the a dictionary with
information about the customer's address. The designers working for
your client want to use information from your system on the client's
website **and** they want to maintain the display code themselves.
Using PSP, the display code for the website might look something like
the following::
<%= self.customer()[ID].address()['city'] %> (42 chars)
Without understanding objects, methods, and dictionaries the designers
must rely on rote memory to know where to put the parentheses,
brackets and quotation marks. NameMapper syntax insulates them from
this complexity and is forgiving with the small syntactical subtleties
that PSP would gag on. All of the following variations are valid with
NameMapper::
$self.customers()[$ID].address()['city'] (39 chars)
--OR--
$customers()[$ID].address()['city']
--OR--
$customers()[$ID].address().city
--OR--
$customers()[$ID].address.city
--OR--
$customers()[$ID].address.city
--OR--
$customers[$ID].address.city (27 chars)
NameMapper may initially scare those who have experienced the quirks
of Zope's ``acquisition`` and ``DTML``. If desired, it can be turned
off using the ``'useNameMapper'`` setting. However, we have yet to
find a case where this is needed, as plain Python syntax is compatible
with NameMapper.
Cheetah comes with two implementations of the module that implements
NameMapper syntax: one in C and the other in Python. The C version is
up to 6 times faster. It's still slightly slower than standard Python
syntax, but the speed difference is neglible in real world
scenarios. Cheetah uses the optimized C version if it has been
compiled, and automatically falls back to the Python version if not.
Completely Configurable Syntax
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We made the potentially controversial decision to allow users to use
any delimeters in place of the standard ``$`` and ``#``. The
delimeters for comments are also configurable. This is a safety valve
for situations we didn't anticipate, where the standard delimiters are
just too impractical. We wanted something that is flexible enough to
handle unanticipated situations in the future. In particular,
configurable delimiters allow for the dynamic creation of tutorials,
program listings, or even source code listings of other languages that
use $ for special purposes. For example, preprocessing of LaTeX
source files is one area that we have a strong interest in.
People often raise concerns that this might lead to conflicts between
templates configured to use different delimiters. This is not a
problem, as each Cheetah template is individually compiled to pure
Python code. Thus, such changes are localized to a single file. A
template with one set of delimiters can safely subclass or even
include a template that uses another set. A very unique feature of
Cheetah is that these delimiters can be changed for an entire template
file, or just a particular portion of a template.
Powerful Output Caching Framework
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cheetah can cache the output from individual ``$placeholder`` tags or
from entire regions of Cheetah source code. Caches can static or
linked to a refresh-timer.
The syntax for using the caching framework is incredibly simple. To
cache a placeholder statically, use ``$*varName``. To cache a
placeholder with a refresh-timer, use ``$*5m*varName`` (or
``$*5m*{varName}``). Times may be specified in fractional numbers and
with various interval suffixes ('s' for seconds, 'h' for hours, 'd'
for days, 'w' for weeks). Thus, ``$*0.5d*varName`` means cache for
half a day. To cache a region, use the ``#cache`` directive. Cached
regions can be given an ``id``, which can be used to programmatically
refresh a region. Cached regions can also be invalidated according to
user-specified test conditions.
::
#cache id='sidebar', test=$isDBUpdated
## do something
#end cache
#cache id='sidebar', test=($isDBUpdated or $someOtherCondition_)
## do something
#end cache
The ``#cache`` directive is currently undergoing some extensions.
Proposed extensions would allow caching according to a query-string
parameter (``varyByParam='ID'``), the browser type
(``varyByBrowser``).
Tight Integration with Webware, While Being Separable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cheetah integrates tightly with the Webware servlet model, but can
also be used as a standalone tool or as part of other systems. For,
example every template processed with Cheetah has a builtin command
line interface for use with shell scripts.
Current Status
--------------
As of this writing (December 2001), Cheetah is beta software, although
it is already being used in several production applications. The
existing syntax and semantics are stable, but there are some additions
to come. Version 1.0 will be released "when it's ready". We're hoping
that will be early 2002, but we would rather "do it right" than "do it
hastily". Our goals are robustness, scalability, and applicability to
a wide variety of situations.
Future Directions
-----------------
WYSIWYG Editor Integration
~~~~~~~~~~~~~~~~~~~~~~~~~~
While effort has been made to make Cheetah friendly with WYSIWYG
systems, the test of actual use has yet to be made. In particular,
WYSIWYG editors generally demand to make valid HTML documents, and
that can conflict with Cheetah as it conflicts with many template
systems. The most difficult problem is a case like::
#for $row in $values
| $row |
#end for
Anything between and is generally invalid, and WYSIWYG
editors will rearrange these pieces.
More Documentation
~~~~~~~~~~~~~~~~~~
Cheetah currently has a 60 page Users' Guide (see the Cheetah home
page) and a collaborative Wiki. A Beginners' Guide, and a Developers'
Guide are planned. The wiki site is home to an Examples Cookbook
describing various usage strategies
Code and Template Libraries
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cheetah comes "batteries included" with a library of templates,
functions and other objects you can use in your own programs. Some of
these were contributed by users. We are very interested in building
up this library.
Conclusion
----------
Like its namesake, Cheetah is fast, flexible and powerful. If you're a
Python programmer looking for a templating system that is Pythonic,
consider Cheetah. If you like Webware because of its modular nature,
you'll like Cheetah. If you require a division of labor between the
application programmers and designers (who may not know how to
program), Cheetah is a good match.
Acknowledgments
---------------
Chuck Esterbrook provided the original inspiration for Cheetah and has
been an active contributor throughout the project.
We'd also like to thank the following people for contributing valuable
advice, code and encouragement: Geoff Talvola, Jeff Johnson, Graham
Dumpleton, Clark C. Evans, Craig Kattner, Franz Geiger, Tom Schwaller,
Rober Kuzelj, Jay Love, Terrel Shumway, Sasa Zivkov, Arkaitz Bitorika,
Jeremiah Bellomy, Baruch Even, Paul Boddie, Stephan Diehl, Chui Tey,
and Geir Magnusson.
The Velocity and WebMacro_ projects provided inspiration and design ideas.
Cheetah has benefitted from the creativity and energy of their developers.
Appendix
--------
Detailed Example
~~~~~~~~~~~~~~~~
The following template is part of Cheetah's standard library::
#*doc-module:
A Skeleton HTML page template, that provides basic structure and utility methods. *#
################################################################################
#from Cheetah.Templates._SkeletonPage_ import _SkeletonPage_
#extends _SkeletonPage_
#implements respond
################################################################################
#settings python
title = 'Skeleton Page Template'
siteDomainName_ = 'www.CheetahTemplate_.org'
siteCopyrightName_ = 'Tavis Rudd'
siteCredits = 'Designed & Implemented by Tavis Rudd'
metaTags = {'HTTP_EQUIV':{'keywords':'Cheetah,'},
'NAME':{'generator':'Cheetah: The Python-Powered Template Engine'}
}
bodyTagAttribs_ = {'text':'black'}
#end settings
################################################################################
#cache id='header'
$docType
################################################################################
#block writeHeadTag_
$title
$metaTags
$stylesheetTags
$javascriptTags
#end block writeHeadTag_
#end cache
$bodyTag
#block writeBody
This skeleton page has no flesh. Its body needs to be implemented.
#end block writeBody