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