As we have seen, a web resource consists of a template and a handler function, with any of the two but not both possibly being empty. Templates have a default extension of .dsp that can be changed in the Draco configuration file. An http request is always made to the full template name, including extension.
Templates are regular html files that contain special blocks that are marked by
<%
and %>
markers. Currently there are three types of blocks.
Directive blocks are blocks starting with an "at" sign (@
). They
contain instructions for to the Draco parser to take some special action.
Currently there is only one directive defined: the include
directive.
This directive tells Draco to include another file in the template. The file
that is to be included is specified by a file
or by an expr
attribute. The file
attribute should specify the name of the file to
include, while the expr
attribute should be a Python expression that
is evaluates to a file name. File names can be absolute or relative path
names. Absolute path names are taken relative to the document root of the
website while relative path names are relative to the directory of the
template.
The template fragment:
<%@ include file="/header.inc" %>
includes a file named header.inc from the document root of the web site.
Expression blocks are blocks starting with an "equal" sign (=
)
and contain a single Python expression. The expression is evaluated by Draco
and the result is substituted back into the template. All variables from the
interface are available in the expression. If the expression evaluates to
something that is not a string, the result is converted to a string by
calling str()
.
Undefined variables in an expression block are initialized to the empty
string. This is very handy when, for example, providing form feedback. You
don't have to check first if a parameter was given, you can just evaluate
its corresponding variable. The choice for the empty string was a deliberate
one. It might seem more ``correct'' to initialize to None
, but in an
expression block that would evaluate to the string "None"
. An empty
string in much more neutral in a template.
The example below displays the value of the variable status
if that
variable has been set:
<%= status %>
Code blocks are blocks that are not marked by any special character. They
contain a fragment of Python code that is run by Draco. The code can output
results by using print
or sys.stdout.write()
and these results
are substituted back into the template. As with expressions, all variables in
the interface are available to the code fragment. Undefined variables in the
code block are initialized to the empty string.
The example below display the string "Hello, world!":
<% print "Hello, world!" %>
In code blocks, special care must be taken to indentation. You are likely using indentation for structuring your html, but, as we know, Python uses indentation for grouping too! Draco uses a simple but effective approach to indenting. The first nonempty line in a code block determines the indentation level and this many spaces are removed from each line in the block. If you use tabs, they are converted to 8 spaces before de-indenting. This is the same value that Python itself uses.
Note:
Some web content systems have a way to ``escape to html mode''.
This means that inside a code block you can go back to html mode, even when
a language statement (like a for
loop) is not complete. The statement
is then ended inside another code block. This functionality is not
available in Draco for two reasons. First, I found that code legibility did
not improve with this and that a print statement with a multiline string was
just the same thing. The second reason is more technical. Python doesn't
have an "end of block" marker. Instead, the end of block is marked by going
back one indentation level. This gets ugly in a template because you now
have to compare indentation levels of two different code blocks. Some other
web content systems using Python define a special "end of block" marker. I
think this is ugly too because it is just what Python tries to eliminate.