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, 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  # py2
00206 except ImportError:
00207     from io import StringIO  # py3
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     # note that the constructor's signature is not extracted with
00220     # autodoc because _UNSET looks like garbage.  When changing
00221     # this signature update website/sphinx/template.rst too.
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             # Under python2.5, the fake filename used here must match
00241             # the module name used in __name__ below.
00242             # The dont_inherit flag prevents template.py's future imports
00243             # from being applied to the generated code.
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,  # for internal use
00264             "_tt_string_types": (unicode_type, bytes_type),
00265             # __name__ and __loader__ allow the traceback mechanism to find
00266             # the generated source code.
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         # Clear the traceback module's cache of source data now that
00275         # we've generated a new template (mainly for this module's
00276         # unittests, where different tests reuse the same name).
00277         linecache.clearcache()
00278         return execute()
00279 
00280     def _generate_python(self, loader, compress_whitespace):
00281         buffer = StringIO()
00282         try:
00283             # named_blocks maps from names to _NamedBlock objects
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         # self.lock protects self.templates.  It's a reentrant lock
00323         # because templates may load other templates via `include` or
00324         # `extends`.  Note that thanks to the GIL this code would be safe
00325         # even without the lock, but could lead to wasted work as multiple
00326         # threads tried to compile the same template simultaneously.
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             # Just in case the body was empty
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         # In case the previous block was empty
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             # In python3 functions like xhtml_escape return unicode,
00548             # so we have to convert to utf8 again.
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         # Compress lots of white space to a single character. If the whitespace
00569         # breaks a line, have it continue to break a line, but just with a
00570         # single \n character
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         # Find next template directive
00701         curly = 0
00702         while True:
00703             curly = reader.find("{", curly)
00704             if curly == -1 or curly + 1 == reader.remaining():
00705                 # EOF
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             # If the first curly brace is not the start of a special token,
00712             # start searching from the character after it
00713             if reader[curly + 1] not in ("{", "%", "#"):
00714                 curly += 1
00715                 continue
00716             # When there are more than 2 curlies in a row, use the
00717             # innermost ones.  This is useful when generating languages
00718             # like latex where curlies are also meaningful
00719             if (curly + 2 < reader.remaining() and
00720                     reader[curly + 1] == '{' and reader[curly + 2] == '{'):
00721                 curly += 1
00722                 continue
00723             break
00724 
00725         # Append any text before the special token
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         # Template directives may be escaped as "{{!" or "{%!".
00734         # In this case output the braces and consume the "!".
00735         # This is especially useful in conjunction with jquery templates,
00736         # which also use double braces.
00737         if reader.remaining() and reader[0] == "!":
00738             reader.consume(1)
00739             body.chunks.append(_Text(start_brace, line))
00740             continue
00741 
00742         # Comment
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         # Expression
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         # Block
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         # Intermediate ("else", "elif", etc) blocks
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         # End tag
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             # parse inner body recursively
00836             if operator in ("for", "while"):
00837                 block_body = _parse(reader, template, operator, operator)
00838             elif operator == "apply":
00839                 # apply creates a nested function so syntactically it's not
00840                 # in the loop.
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)


rosbridge_server
Author(s): Jonathan Mace
autogenerated on Wed Sep 13 2017 03:18:20