00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 """A simple template system that compiles templates to Python code.
00018
00019 Basic usage looks like::
00020
00021 t = template.Template("<html>{{ myvalue }}</html>")
00022 print t.generate(myvalue="XXX")
00023
00024 `Loader` is a class that loads templates from a root directory and caches
00025 the compiled templates::
00026
00027 loader = template.Loader("/home/btaylor")
00028 print loader.load("test.html").generate(myvalue="XXX")
00029
00030 We compile all templates to raw Python. Error-reporting is currently... uh,
00031 interesting. Syntax for the templates::
00032
00033 ### base.html
00034 <html>
00035 <head>
00036 <title>{% block title %}Default title{% end %}</title>
00037 </head>
00038 <body>
00039 <ul>
00040 {% for student in students %}
00041 {% block student %}
00042 <li>{{ escape(student.name) }}</li>
00043 {% end %}
00044 {% end %}
00045 </ul>
00046 </body>
00047 </html>
00048
00049 ### bold.html
00050 {% extends "base.html" %}
00051
00052 {% block title %}A bolder title{% end %}
00053
00054 {% block student %}
00055 <li><span style="bold">{{ escape(student.name) }}</span></li>
00056 {% end %}
00057
00058 Unlike most other template systems, we do not put any restrictions on the
00059 expressions you can include in your statements. ``if`` and ``for`` blocks get
00060 translated exactly into Python, so you can do complex expressions like::
00061
00062 {% for student in [p for p in people if p.student and p.age > 23] %}
00063 <li>{{ escape(student.name) }}</li>
00064 {% end %}
00065
00066 Translating directly to Python means you can apply functions to expressions
00067 easily, like the ``escape()`` function in the examples above. You can pass
00068 functions in to your template just like any other variable
00069 (In a `.RequestHandler`, override `.RequestHandler.get_template_namespace`)::
00070
00071 ### Python code
00072 def add(x, y):
00073 return x + y
00074 template.execute(add=add)
00075
00076 ### The template
00077 {{ add(1, 2) }}
00078
00079 We provide the functions `escape() <.xhtml_escape>`, `.url_escape()`,
00080 `.json_encode()`, and `.squeeze()` to all templates by default.
00081
00082 Typical applications do not create `Template` or `Loader` instances by
00083 hand, but instead use the `~.RequestHandler.render` and
00084 `~.RequestHandler.render_string` methods of
00085 `tornado.web.RequestHandler`, which load templates automatically based
00086 on the ``template_path`` `.Application` setting.
00087
00088 Variable names beginning with ``_tt_`` are reserved by the template
00089 system and should not be used by application code.
00090
00091 Syntax Reference
00092 ----------------
00093
00094 Template expressions are surrounded by double curly braces: ``{{ ... }}``.
00095 The contents may be any python expression, which will be escaped according
00096 to the current autoescape setting and inserted into the output. Other
00097 template directives use ``{% %}``. These tags may be escaped as ``{{!``
00098 and ``{%!`` if you need to include a literal ``{{`` or ``{%`` in the output.
00099
00100 To comment out a section so that it is omitted from the output, surround it
00101 with ``{# ... #}``.
00102
00103 ``{% apply *function* %}...{% end %}``
00104 Applies a function to the output of all template code between ``apply``
00105 and ``end``::
00106
00107 {% apply linkify %}{{name}} said: {{message}}{% end %}
00108
00109 Note that as an implementation detail apply blocks are implemented
00110 as nested functions and thus may interact strangely with variables
00111 set via ``{% set %}``, or the use of ``{% break %}`` or ``{% continue %}``
00112 within loops.
00113
00114 ``{% autoescape *function* %}``
00115 Sets the autoescape mode for the current file. This does not affect
00116 other files, even those referenced by ``{% include %}``. Note that
00117 autoescaping can also be configured globally, at the `.Application`
00118 or `Loader`.::
00119
00120 {% autoescape xhtml_escape %}
00121 {% autoescape None %}
00122
00123 ``{% block *name* %}...{% end %}``
00124 Indicates a named, replaceable block for use with ``{% extends %}``.
00125 Blocks in the parent template will be replaced with the contents of
00126 the same-named block in a child template.::
00127
00128 <!-- base.html -->
00129 <title>{% block title %}Default title{% end %}</title>
00130
00131 <!-- mypage.html -->
00132 {% extends "base.html" %}
00133 {% block title %}My page title{% end %}
00134
00135 ``{% comment ... %}``
00136 A comment which will be removed from the template output. Note that
00137 there is no ``{% end %}`` tag; the comment goes from the word ``comment``
00138 to the closing ``%}`` tag.
00139
00140 ``{% extends *filename* %}``
00141 Inherit from another template. Templates that use ``extends`` should
00142 contain one or more ``block`` tags to replace content from the parent
00143 template. Anything in the child template not contained in a ``block``
00144 tag will be ignored. For an example, see the ``{% block %}`` tag.
00145
00146 ``{% for *var* in *expr* %}...{% end %}``
00147 Same as the python ``for`` statement. ``{% break %}`` and
00148 ``{% continue %}`` may be used inside the loop.
00149
00150 ``{% from *x* import *y* %}``
00151 Same as the python ``import`` statement.
00152
00153 ``{% if *condition* %}...{% elif *condition* %}...{% else %}...{% end %}``
00154 Conditional statement - outputs the first section whose condition is
00155 true. (The ``elif`` and ``else`` sections are optional)
00156
00157 ``{% import *module* %}``
00158 Same as the python ``import`` statement.
00159
00160 ``{% include *filename* %}``
00161 Includes another template file. The included file can see all the local
00162 variables as if it were copied directly to the point of the ``include``
00163 directive (the ``{% autoescape %}`` directive is an exception).
00164 Alternately, ``{% module Template(filename, **kwargs) %}`` may be used
00165 to include another template with an isolated namespace.
00166
00167 ``{% module *expr* %}``
00168 Renders a `~tornado.web.UIModule`. The output of the ``UIModule`` is
00169 not escaped::
00170
00171 {% module Template("foo.html", arg=42) %}
00172
00173 ``UIModules`` are a feature of the `tornado.web.RequestHandler`
00174 class (and specifically its ``render`` method) and will not work
00175 when the template system is used on its own in other contexts.
00176
00177 ``{% raw *expr* %}``
00178 Outputs the result of the given expression without autoescaping.
00179
00180 ``{% set *x* = *y* %}``
00181 Sets a local variable.
00182
00183 ``{% try %}...{% except %}...{% else %}...{% finally %}...{% end %}``
00184 Same as the python ``try`` statement.
00185
00186 ``{% while *condition* %}... {% end %}``
00187 Same as the python ``while`` statement. ``{% break %}`` and
00188 ``{% continue %}`` may be used inside the loop.
00189 """
00190
00191 from __future__ import absolute_import, division, print_function, with_statement
00192
00193 import datetime
00194 import linecache
00195 import os.path
00196 import posixpath
00197 import re
00198 import threading
00199
00200 from tornado import escape
00201 from tornado.log import app_log
00202 from tornado.util import bytes_type, ObjectDict, exec_in, unicode_type
00203
00204 try:
00205 from cStringIO import StringIO
00206 except ImportError:
00207 from io import StringIO
00208
00209 _DEFAULT_AUTOESCAPE = "xhtml_escape"
00210 _UNSET = object()
00211
00212
00213 class Template(object):
00214 """A compiled template.
00215
00216 We compile into Python from the given template_string. You can generate
00217 the template from variables with generate().
00218 """
00219
00220
00221
00222 def __init__(self, template_string, name="<string>", loader=None,
00223 compress_whitespace=None, autoescape=_UNSET):
00224 self.name = name
00225 if compress_whitespace is None:
00226 compress_whitespace = name.endswith(".html") or \
00227 name.endswith(".js")
00228 if autoescape is not _UNSET:
00229 self.autoescape = autoescape
00230 elif loader:
00231 self.autoescape = loader.autoescape
00232 else:
00233 self.autoescape = _DEFAULT_AUTOESCAPE
00234 self.namespace = loader.namespace if loader else {}
00235 reader = _TemplateReader(name, escape.native_str(template_string))
00236 self.file = _File(self, _parse(reader, self))
00237 self.code = self._generate_python(loader, compress_whitespace)
00238 self.loader = loader
00239 try:
00240
00241
00242
00243
00244 self.compiled = compile(
00245 escape.to_unicode(self.code),
00246 "%s.generated.py" % self.name.replace('.', '_'),
00247 "exec", dont_inherit=True)
00248 except Exception:
00249 formatted_code = _format_code(self.code).rstrip()
00250 app_log.error("%s code:\n%s", self.name, formatted_code)
00251 raise
00252
00253 def generate(self, **kwargs):
00254 """Generate this template with the given arguments."""
00255 namespace = {
00256 "escape": escape.xhtml_escape,
00257 "xhtml_escape": escape.xhtml_escape,
00258 "url_escape": escape.url_escape,
00259 "json_encode": escape.json_encode,
00260 "squeeze": escape.squeeze,
00261 "linkify": escape.linkify,
00262 "datetime": datetime,
00263 "_tt_utf8": escape.utf8,
00264 "_tt_string_types": (unicode_type, bytes_type),
00265
00266
00267 "__name__": self.name.replace('.', '_'),
00268 "__loader__": ObjectDict(get_source=lambda name: self.code),
00269 }
00270 namespace.update(self.namespace)
00271 namespace.update(kwargs)
00272 exec_in(self.compiled, namespace)
00273 execute = namespace["_tt_execute"]
00274
00275
00276
00277 linecache.clearcache()
00278 return execute()
00279
00280 def _generate_python(self, loader, compress_whitespace):
00281 buffer = StringIO()
00282 try:
00283
00284 named_blocks = {}
00285 ancestors = self._get_ancestors(loader)
00286 ancestors.reverse()
00287 for ancestor in ancestors:
00288 ancestor.find_named_blocks(loader, named_blocks)
00289 writer = _CodeWriter(buffer, named_blocks, loader, ancestors[0].template,
00290 compress_whitespace)
00291 ancestors[0].generate(writer)
00292 return buffer.getvalue()
00293 finally:
00294 buffer.close()
00295
00296 def _get_ancestors(self, loader):
00297 ancestors = [self.file]
00298 for chunk in self.file.body.chunks:
00299 if isinstance(chunk, _ExtendsBlock):
00300 if not loader:
00301 raise ParseError("{% extends %} block found, but no "
00302 "template loader")
00303 template = loader.load(chunk.name, self.name)
00304 ancestors.extend(template._get_ancestors(loader))
00305 return ancestors
00306
00307
00308 class BaseLoader(object):
00309 """Base class for template loaders.
00310
00311 You must use a template loader to use template constructs like
00312 ``{% extends %}`` and ``{% include %}``. The loader caches all
00313 templates after they are loaded the first time.
00314 """
00315 def __init__(self, autoescape=_DEFAULT_AUTOESCAPE, namespace=None):
00316 """``autoescape`` must be either None or a string naming a function
00317 in the template namespace, such as "xhtml_escape".
00318 """
00319 self.autoescape = autoescape
00320 self.namespace = namespace or {}
00321 self.templates = {}
00322
00323
00324
00325
00326
00327 self.lock = threading.RLock()
00328
00329 def reset(self):
00330 """Resets the cache of compiled templates."""
00331 with self.lock:
00332 self.templates = {}
00333
00334 def resolve_path(self, name, parent_path=None):
00335 """Converts a possibly-relative path to absolute (used internally)."""
00336 raise NotImplementedError()
00337
00338 def load(self, name, parent_path=None):
00339 """Loads a template."""
00340 name = self.resolve_path(name, parent_path=parent_path)
00341 with self.lock:
00342 if name not in self.templates:
00343 self.templates[name] = self._create_template(name)
00344 return self.templates[name]
00345
00346 def _create_template(self, name):
00347 raise NotImplementedError()
00348
00349
00350 class Loader(BaseLoader):
00351 """A template loader that loads from a single root directory.
00352 """
00353 def __init__(self, root_directory, **kwargs):
00354 super(Loader, self).__init__(**kwargs)
00355 self.root = os.path.abspath(root_directory)
00356
00357 def resolve_path(self, name, parent_path=None):
00358 if parent_path and not parent_path.startswith("<") and \
00359 not parent_path.startswith("/") and \
00360 not name.startswith("/"):
00361 current_path = os.path.join(self.root, parent_path)
00362 file_dir = os.path.dirname(os.path.abspath(current_path))
00363 relative_path = os.path.abspath(os.path.join(file_dir, name))
00364 if relative_path.startswith(self.root):
00365 name = relative_path[len(self.root) + 1:]
00366 return name
00367
00368 def _create_template(self, name):
00369 path = os.path.join(self.root, name)
00370 with open(path, "rb") as f:
00371 template = Template(f.read(), name=name, loader=self)
00372 return template
00373
00374
00375 class DictLoader(BaseLoader):
00376 """A template loader that loads from a dictionary."""
00377 def __init__(self, dict, **kwargs):
00378 super(DictLoader, self).__init__(**kwargs)
00379 self.dict = dict
00380
00381 def resolve_path(self, name, parent_path=None):
00382 if parent_path and not parent_path.startswith("<") and \
00383 not parent_path.startswith("/") and \
00384 not name.startswith("/"):
00385 file_dir = posixpath.dirname(parent_path)
00386 name = posixpath.normpath(posixpath.join(file_dir, name))
00387 return name
00388
00389 def _create_template(self, name):
00390 return Template(self.dict[name], name=name, loader=self)
00391
00392
00393 class _Node(object):
00394 def each_child(self):
00395 return ()
00396
00397 def generate(self, writer):
00398 raise NotImplementedError()
00399
00400 def find_named_blocks(self, loader, named_blocks):
00401 for child in self.each_child():
00402 child.find_named_blocks(loader, named_blocks)
00403
00404
00405 class _File(_Node):
00406 def __init__(self, template, body):
00407 self.template = template
00408 self.body = body
00409 self.line = 0
00410
00411 def generate(self, writer):
00412 writer.write_line("def _tt_execute():", self.line)
00413 with writer.indent():
00414 writer.write_line("_tt_buffer = []", self.line)
00415 writer.write_line("_tt_append = _tt_buffer.append", self.line)
00416 self.body.generate(writer)
00417 writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line)
00418
00419 def each_child(self):
00420 return (self.body,)
00421
00422
00423 class _ChunkList(_Node):
00424 def __init__(self, chunks):
00425 self.chunks = chunks
00426
00427 def generate(self, writer):
00428 for chunk in self.chunks:
00429 chunk.generate(writer)
00430
00431 def each_child(self):
00432 return self.chunks
00433
00434
00435 class _NamedBlock(_Node):
00436 def __init__(self, name, body, template, line):
00437 self.name = name
00438 self.body = body
00439 self.template = template
00440 self.line = line
00441
00442 def each_child(self):
00443 return (self.body,)
00444
00445 def generate(self, writer):
00446 block = writer.named_blocks[self.name]
00447 with writer.include(block.template, self.line):
00448 block.body.generate(writer)
00449
00450 def find_named_blocks(self, loader, named_blocks):
00451 named_blocks[self.name] = self
00452 _Node.find_named_blocks(self, loader, named_blocks)
00453
00454
00455 class _ExtendsBlock(_Node):
00456 def __init__(self, name):
00457 self.name = name
00458
00459
00460 class _IncludeBlock(_Node):
00461 def __init__(self, name, reader, line):
00462 self.name = name
00463 self.template_name = reader.name
00464 self.line = line
00465
00466 def find_named_blocks(self, loader, named_blocks):
00467 included = loader.load(self.name, self.template_name)
00468 included.file.find_named_blocks(loader, named_blocks)
00469
00470 def generate(self, writer):
00471 included = writer.loader.load(self.name, self.template_name)
00472 with writer.include(included, self.line):
00473 included.file.body.generate(writer)
00474
00475
00476 class _ApplyBlock(_Node):
00477 def __init__(self, method, line, body=None):
00478 self.method = method
00479 self.line = line
00480 self.body = body
00481
00482 def each_child(self):
00483 return (self.body,)
00484
00485 def generate(self, writer):
00486 method_name = "_tt_apply%d" % writer.apply_counter
00487 writer.apply_counter += 1
00488 writer.write_line("def %s():" % method_name, self.line)
00489 with writer.indent():
00490 writer.write_line("_tt_buffer = []", self.line)
00491 writer.write_line("_tt_append = _tt_buffer.append", self.line)
00492 self.body.generate(writer)
00493 writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line)
00494 writer.write_line("_tt_append(_tt_utf8(%s(%s())))" % (
00495 self.method, method_name), self.line)
00496
00497
00498 class _ControlBlock(_Node):
00499 def __init__(self, statement, line, body=None):
00500 self.statement = statement
00501 self.line = line
00502 self.body = body
00503
00504 def each_child(self):
00505 return (self.body,)
00506
00507 def generate(self, writer):
00508 writer.write_line("%s:" % self.statement, self.line)
00509 with writer.indent():
00510 self.body.generate(writer)
00511
00512 writer.write_line("pass", self.line)
00513
00514
00515 class _IntermediateControlBlock(_Node):
00516 def __init__(self, statement, line):
00517 self.statement = statement
00518 self.line = line
00519
00520 def generate(self, writer):
00521
00522 writer.write_line("pass", self.line)
00523 writer.write_line("%s:" % self.statement, self.line, writer.indent_size() - 1)
00524
00525
00526 class _Statement(_Node):
00527 def __init__(self, statement, line):
00528 self.statement = statement
00529 self.line = line
00530
00531 def generate(self, writer):
00532 writer.write_line(self.statement, self.line)
00533
00534
00535 class _Expression(_Node):
00536 def __init__(self, expression, line, raw=False):
00537 self.expression = expression
00538 self.line = line
00539 self.raw = raw
00540
00541 def generate(self, writer):
00542 writer.write_line("_tt_tmp = %s" % self.expression, self.line)
00543 writer.write_line("if isinstance(_tt_tmp, _tt_string_types):"
00544 " _tt_tmp = _tt_utf8(_tt_tmp)", self.line)
00545 writer.write_line("else: _tt_tmp = _tt_utf8(str(_tt_tmp))", self.line)
00546 if not self.raw and writer.current_template.autoescape is not None:
00547
00548
00549 writer.write_line("_tt_tmp = _tt_utf8(%s(_tt_tmp))" %
00550 writer.current_template.autoescape, self.line)
00551 writer.write_line("_tt_append(_tt_tmp)", self.line)
00552
00553
00554 class _Module(_Expression):
00555 def __init__(self, expression, line):
00556 super(_Module, self).__init__("_tt_modules." + expression, line,
00557 raw=True)
00558
00559
00560 class _Text(_Node):
00561 def __init__(self, value, line):
00562 self.value = value
00563 self.line = line
00564
00565 def generate(self, writer):
00566 value = self.value
00567
00568
00569
00570
00571 if writer.compress_whitespace and "<pre>" not in value:
00572 value = re.sub(r"([\t ]+)", " ", value)
00573 value = re.sub(r"(\s*\n\s*)", "\n", value)
00574
00575 if value:
00576 writer.write_line('_tt_append(%r)' % escape.utf8(value), self.line)
00577
00578
00579 class ParseError(Exception):
00580 """Raised for template syntax errors."""
00581 pass
00582
00583
00584 class _CodeWriter(object):
00585 def __init__(self, file, named_blocks, loader, current_template,
00586 compress_whitespace):
00587 self.file = file
00588 self.named_blocks = named_blocks
00589 self.loader = loader
00590 self.current_template = current_template
00591 self.compress_whitespace = compress_whitespace
00592 self.apply_counter = 0
00593 self.include_stack = []
00594 self._indent = 0
00595
00596 def indent_size(self):
00597 return self._indent
00598
00599 def indent(self):
00600 class Indenter(object):
00601 def __enter__(_):
00602 self._indent += 1
00603 return self
00604
00605 def __exit__(_, *args):
00606 assert self._indent > 0
00607 self._indent -= 1
00608
00609 return Indenter()
00610
00611 def include(self, template, line):
00612 self.include_stack.append((self.current_template, line))
00613 self.current_template = template
00614
00615 class IncludeTemplate(object):
00616 def __enter__(_):
00617 return self
00618
00619 def __exit__(_, *args):
00620 self.current_template = self.include_stack.pop()[0]
00621
00622 return IncludeTemplate()
00623
00624 def write_line(self, line, line_number, indent=None):
00625 if indent is None:
00626 indent = self._indent
00627 line_comment = ' # %s:%d' % (self.current_template.name, line_number)
00628 if self.include_stack:
00629 ancestors = ["%s:%d" % (tmpl.name, lineno)
00630 for (tmpl, lineno) in self.include_stack]
00631 line_comment += ' (via %s)' % ', '.join(reversed(ancestors))
00632 print(" " * indent + line + line_comment, file=self.file)
00633
00634
00635 class _TemplateReader(object):
00636 def __init__(self, name, text):
00637 self.name = name
00638 self.text = text
00639 self.line = 1
00640 self.pos = 0
00641
00642 def find(self, needle, start=0, end=None):
00643 assert start >= 0, start
00644 pos = self.pos
00645 start += pos
00646 if end is None:
00647 index = self.text.find(needle, start)
00648 else:
00649 end += pos
00650 assert end >= start
00651 index = self.text.find(needle, start, end)
00652 if index != -1:
00653 index -= pos
00654 return index
00655
00656 def consume(self, count=None):
00657 if count is None:
00658 count = len(self.text) - self.pos
00659 newpos = self.pos + count
00660 self.line += self.text.count("\n", self.pos, newpos)
00661 s = self.text[self.pos:newpos]
00662 self.pos = newpos
00663 return s
00664
00665 def remaining(self):
00666 return len(self.text) - self.pos
00667
00668 def __len__(self):
00669 return self.remaining()
00670
00671 def __getitem__(self, key):
00672 if type(key) is slice:
00673 size = len(self)
00674 start, stop, step = key.indices(size)
00675 if start is None:
00676 start = self.pos
00677 else:
00678 start += self.pos
00679 if stop is not None:
00680 stop += self.pos
00681 return self.text[slice(start, stop, step)]
00682 elif key < 0:
00683 return self.text[key]
00684 else:
00685 return self.text[self.pos + key]
00686
00687 def __str__(self):
00688 return self.text[self.pos:]
00689
00690
00691 def _format_code(code):
00692 lines = code.splitlines()
00693 format = "%%%dd %%s\n" % len(repr(len(lines) + 1))
00694 return "".join([format % (i + 1, line) for (i, line) in enumerate(lines)])
00695
00696
00697 def _parse(reader, template, in_block=None, in_loop=None):
00698 body = _ChunkList([])
00699 while True:
00700
00701 curly = 0
00702 while True:
00703 curly = reader.find("{", curly)
00704 if curly == -1 or curly + 1 == reader.remaining():
00705
00706 if in_block:
00707 raise ParseError("Missing {%% end %%} block for %s" %
00708 in_block)
00709 body.chunks.append(_Text(reader.consume(), reader.line))
00710 return body
00711
00712
00713 if reader[curly + 1] not in ("{", "%", "#"):
00714 curly += 1
00715 continue
00716
00717
00718
00719 if (curly + 2 < reader.remaining() and
00720 reader[curly + 1] == '{' and reader[curly + 2] == '{'):
00721 curly += 1
00722 continue
00723 break
00724
00725
00726 if curly > 0:
00727 cons = reader.consume(curly)
00728 body.chunks.append(_Text(cons, reader.line))
00729
00730 start_brace = reader.consume(2)
00731 line = reader.line
00732
00733
00734
00735
00736
00737 if reader.remaining() and reader[0] == "!":
00738 reader.consume(1)
00739 body.chunks.append(_Text(start_brace, line))
00740 continue
00741
00742
00743 if start_brace == "{#":
00744 end = reader.find("#}")
00745 if end == -1:
00746 raise ParseError("Missing end expression #} on line %d" % line)
00747 contents = reader.consume(end).strip()
00748 reader.consume(2)
00749 continue
00750
00751
00752 if start_brace == "{{":
00753 end = reader.find("}}")
00754 if end == -1:
00755 raise ParseError("Missing end expression }} on line %d" % line)
00756 contents = reader.consume(end).strip()
00757 reader.consume(2)
00758 if not contents:
00759 raise ParseError("Empty expression on line %d" % line)
00760 body.chunks.append(_Expression(contents, line))
00761 continue
00762
00763
00764 assert start_brace == "{%", start_brace
00765 end = reader.find("%}")
00766 if end == -1:
00767 raise ParseError("Missing end block %%} on line %d" % line)
00768 contents = reader.consume(end).strip()
00769 reader.consume(2)
00770 if not contents:
00771 raise ParseError("Empty block tag ({%% %%}) on line %d" % line)
00772
00773 operator, space, suffix = contents.partition(" ")
00774 suffix = suffix.strip()
00775
00776
00777 intermediate_blocks = {
00778 "else": set(["if", "for", "while", "try"]),
00779 "elif": set(["if"]),
00780 "except": set(["try"]),
00781 "finally": set(["try"]),
00782 }
00783 allowed_parents = intermediate_blocks.get(operator)
00784 if allowed_parents is not None:
00785 if not in_block:
00786 raise ParseError("%s outside %s block" %
00787 (operator, allowed_parents))
00788 if in_block not in allowed_parents:
00789 raise ParseError("%s block cannot be attached to %s block" % (operator, in_block))
00790 body.chunks.append(_IntermediateControlBlock(contents, line))
00791 continue
00792
00793
00794 elif operator == "end":
00795 if not in_block:
00796 raise ParseError("Extra {%% end %%} block on line %d" % line)
00797 return body
00798
00799 elif operator in ("extends", "include", "set", "import", "from",
00800 "comment", "autoescape", "raw", "module"):
00801 if operator == "comment":
00802 continue
00803 if operator == "extends":
00804 suffix = suffix.strip('"').strip("'")
00805 if not suffix:
00806 raise ParseError("extends missing file path on line %d" % line)
00807 block = _ExtendsBlock(suffix)
00808 elif operator in ("import", "from"):
00809 if not suffix:
00810 raise ParseError("import missing statement on line %d" % line)
00811 block = _Statement(contents, line)
00812 elif operator == "include":
00813 suffix = suffix.strip('"').strip("'")
00814 if not suffix:
00815 raise ParseError("include missing file path on line %d" % line)
00816 block = _IncludeBlock(suffix, reader, line)
00817 elif operator == "set":
00818 if not suffix:
00819 raise ParseError("set missing statement on line %d" % line)
00820 block = _Statement(suffix, line)
00821 elif operator == "autoescape":
00822 fn = suffix.strip()
00823 if fn == "None":
00824 fn = None
00825 template.autoescape = fn
00826 continue
00827 elif operator == "raw":
00828 block = _Expression(suffix, line, raw=True)
00829 elif operator == "module":
00830 block = _Module(suffix, line)
00831 body.chunks.append(block)
00832 continue
00833
00834 elif operator in ("apply", "block", "try", "if", "for", "while"):
00835
00836 if operator in ("for", "while"):
00837 block_body = _parse(reader, template, operator, operator)
00838 elif operator == "apply":
00839
00840
00841 block_body = _parse(reader, template, operator, None)
00842 else:
00843 block_body = _parse(reader, template, operator, in_loop)
00844
00845 if operator == "apply":
00846 if not suffix:
00847 raise ParseError("apply missing method name on line %d" % line)
00848 block = _ApplyBlock(suffix, line, block_body)
00849 elif operator == "block":
00850 if not suffix:
00851 raise ParseError("block missing name on line %d" % line)
00852 block = _NamedBlock(suffix, block_body, template, line)
00853 else:
00854 block = _ControlBlock(contents, line, block_body)
00855 body.chunks.append(block)
00856 continue
00857
00858 elif operator in ("break", "continue"):
00859 if not in_loop:
00860 raise ParseError("%s outside %s block" % (operator, set(["for", "while"])))
00861 body.chunks.append(_Statement(contents, line))
00862 continue
00863
00864 else:
00865 raise ParseError("unknown operator: %r" % operator)