template.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright 2009 Facebook
00004 #
00005 # Licensed under the Apache License, Version 2.0 (the "License"); you may
00006 # not use this file except in compliance with the License. You may obtain
00007 # a copy of the License at
00008 #
00009 #     http://www.apache.org/licenses/LICENSE-2.0
00010 #
00011 # Unless required by applicable law or agreed to in writing, software
00012 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
00013 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
00014 # License for the specific language governing permissions and limitations
00015 # under the License.
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             # Under python2.5, the fake filename used here must match
00218             # the module name used in __name__ below.
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,  # for internal use
00239             "_string_types": (unicode, bytes_type),
00240             # __name__ and __loader__ allow the traceback mechanism to find
00241             # the generated source code.
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         # Clear the traceback module's cache of source data now that
00250         # we've generated a new template (mainly for this module's
00251         # unittests, where different tests reuse the same name).
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             # named_blocks maps from names to _NamedBlock objects
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         # self.lock protects self.templates.  It's a reentrant lock
00304         # because templates may load other templates via `include` or
00305         # `extends`.  Note that thanks to the GIL this code would be safe
00306         # even without the lock, but could lead to wasted work as multiple
00307         # threads tried to compile the same template simultaneously.
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             # In python3 functions like xhtml_escape return unicode,
00530             # so we have to convert to utf8 again.
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         # Compress lots of white space to a single character. If the whitespace
00551         # breaks a line, have it continue to break a line, but just with a
00552         # single \n character
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         # Find next template directive
00683         curly = 0
00684         while True:
00685             curly = reader.find("{", curly)
00686             if curly == -1 or curly + 1 == reader.remaining():
00687                 # EOF
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             # If the first curly brace is not the start of a special token,
00694             # start searching from the character after it
00695             if reader[curly + 1] not in ("{", "%", "#"):
00696                 curly += 1
00697                 continue
00698             # When there are more than 2 curlies in a row, use the
00699             # innermost ones.  This is useful when generating languages
00700             # like latex where curlies are also meaningful
00701             if (curly + 2 < reader.remaining() and
00702                 reader[curly + 1] == '{' and reader[curly + 2] == '{'):
00703                 curly += 1
00704                 continue
00705             break
00706 
00707         # Append any text before the special token
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         # Template directives may be escaped as "{{!" or "{%!".
00716         # In this case output the braces and consume the "!".
00717         # This is especially useful in conjunction with jquery templates,
00718         # which also use double braces.
00719         if reader.remaining() and reader[0] == "!":
00720             reader.consume(1)
00721             body.chunks.append(_Text(start_brace, line))
00722             continue
00723 
00724         # Comment
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         # Expression
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         # Block
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         # Intermediate ("else", "elif", etc) blocks
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         # End tag
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             # parse inner body recursively
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)


roswww
Author(s): Jonathan Mace
autogenerated on Thu Jan 2 2014 11:53:30