template.py 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  1. #
  2. # Copyright 2009 Facebook
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. """A simple template system that compiles templates to Python code.
  16. Basic usage looks like::
  17. t = template.Template("<html>{{ myvalue }}</html>")
  18. print(t.generate(myvalue="XXX"))
  19. `Loader` is a class that loads templates from a root directory and caches
  20. the compiled templates::
  21. loader = template.Loader("/home/btaylor")
  22. print(loader.load("test.html").generate(myvalue="XXX"))
  23. We compile all templates to raw Python. Error-reporting is currently... uh,
  24. interesting. Syntax for the templates::
  25. ### base.html
  26. <html>
  27. <head>
  28. <title>{% block title %}Default title{% end %}</title>
  29. </head>
  30. <body>
  31. <ul>
  32. {% for student in students %}
  33. {% block student %}
  34. <li>{{ escape(student.name) }}</li>
  35. {% end %}
  36. {% end %}
  37. </ul>
  38. </body>
  39. </html>
  40. ### bold.html
  41. {% extends "base.html" %}
  42. {% block title %}A bolder title{% end %}
  43. {% block student %}
  44. <li><span style="bold">{{ escape(student.name) }}</span></li>
  45. {% end %}
  46. Unlike most other template systems, we do not put any restrictions on the
  47. expressions you can include in your statements. ``if`` and ``for`` blocks get
  48. translated exactly into Python, so you can do complex expressions like::
  49. {% for student in [p for p in people if p.student and p.age > 23] %}
  50. <li>{{ escape(student.name) }}</li>
  51. {% end %}
  52. Translating directly to Python means you can apply functions to expressions
  53. easily, like the ``escape()`` function in the examples above. You can pass
  54. functions in to your template just like any other variable
  55. (In a `.RequestHandler`, override `.RequestHandler.get_template_namespace`)::
  56. ### Python code
  57. def add(x, y):
  58. return x + y
  59. template.execute(add=add)
  60. ### The template
  61. {{ add(1, 2) }}
  62. We provide the functions `escape() <.xhtml_escape>`, `.url_escape()`,
  63. `.json_encode()`, and `.squeeze()` to all templates by default.
  64. Typical applications do not create `Template` or `Loader` instances by
  65. hand, but instead use the `~.RequestHandler.render` and
  66. `~.RequestHandler.render_string` methods of
  67. `tornado.web.RequestHandler`, which load templates automatically based
  68. on the ``template_path`` `.Application` setting.
  69. Variable names beginning with ``_tt_`` are reserved by the template
  70. system and should not be used by application code.
  71. Syntax Reference
  72. ----------------
  73. Template expressions are surrounded by double curly braces: ``{{ ... }}``.
  74. The contents may be any python expression, which will be escaped according
  75. to the current autoescape setting and inserted into the output. Other
  76. template directives use ``{% %}``.
  77. To comment out a section so that it is omitted from the output, surround it
  78. with ``{# ... #}``.
  79. To include a literal ``{{``, ``{%``, or ``{#`` in the output, escape them as
  80. ``{{!``, ``{%!``, and ``{#!``, respectively.
  81. ``{% apply *function* %}...{% end %}``
  82. Applies a function to the output of all template code between ``apply``
  83. and ``end``::
  84. {% apply linkify %}{{name}} said: {{message}}{% end %}
  85. Note that as an implementation detail apply blocks are implemented
  86. as nested functions and thus may interact strangely with variables
  87. set via ``{% set %}``, or the use of ``{% break %}`` or ``{% continue %}``
  88. within loops.
  89. ``{% autoescape *function* %}``
  90. Sets the autoescape mode for the current file. This does not affect
  91. other files, even those referenced by ``{% include %}``. Note that
  92. autoescaping can also be configured globally, at the `.Application`
  93. or `Loader`.::
  94. {% autoescape xhtml_escape %}
  95. {% autoescape None %}
  96. ``{% block *name* %}...{% end %}``
  97. Indicates a named, replaceable block for use with ``{% extends %}``.
  98. Blocks in the parent template will be replaced with the contents of
  99. the same-named block in a child template.::
  100. <!-- base.html -->
  101. <title>{% block title %}Default title{% end %}</title>
  102. <!-- mypage.html -->
  103. {% extends "base.html" %}
  104. {% block title %}My page title{% end %}
  105. ``{% comment ... %}``
  106. A comment which will be removed from the template output. Note that
  107. there is no ``{% end %}`` tag; the comment goes from the word ``comment``
  108. to the closing ``%}`` tag.
  109. ``{% extends *filename* %}``
  110. Inherit from another template. Templates that use ``extends`` should
  111. contain one or more ``block`` tags to replace content from the parent
  112. template. Anything in the child template not contained in a ``block``
  113. tag will be ignored. For an example, see the ``{% block %}`` tag.
  114. ``{% for *var* in *expr* %}...{% end %}``
  115. Same as the python ``for`` statement. ``{% break %}`` and
  116. ``{% continue %}`` may be used inside the loop.
  117. ``{% from *x* import *y* %}``
  118. Same as the python ``import`` statement.
  119. ``{% if *condition* %}...{% elif *condition* %}...{% else %}...{% end %}``
  120. Conditional statement - outputs the first section whose condition is
  121. true. (The ``elif`` and ``else`` sections are optional)
  122. ``{% import *module* %}``
  123. Same as the python ``import`` statement.
  124. ``{% include *filename* %}``
  125. Includes another template file. The included file can see all the local
  126. variables as if it were copied directly to the point of the ``include``
  127. directive (the ``{% autoescape %}`` directive is an exception).
  128. Alternately, ``{% module Template(filename, **kwargs) %}`` may be used
  129. to include another template with an isolated namespace.
  130. ``{% module *expr* %}``
  131. Renders a `~tornado.web.UIModule`. The output of the ``UIModule`` is
  132. not escaped::
  133. {% module Template("foo.html", arg=42) %}
  134. ``UIModules`` are a feature of the `tornado.web.RequestHandler`
  135. class (and specifically its ``render`` method) and will not work
  136. when the template system is used on its own in other contexts.
  137. ``{% raw *expr* %}``
  138. Outputs the result of the given expression without autoescaping.
  139. ``{% set *x* = *y* %}``
  140. Sets a local variable.
  141. ``{% try %}...{% except %}...{% else %}...{% finally %}...{% end %}``
  142. Same as the python ``try`` statement.
  143. ``{% while *condition* %}... {% end %}``
  144. Same as the python ``while`` statement. ``{% break %}`` and
  145. ``{% continue %}`` may be used inside the loop.
  146. ``{% whitespace *mode* %}``
  147. Sets the whitespace mode for the remainder of the current file
  148. (or until the next ``{% whitespace %}`` directive). See
  149. `filter_whitespace` for available options. New in Tornado 4.3.
  150. """
  151. import datetime
  152. from io import StringIO
  153. import linecache
  154. import os.path
  155. import posixpath
  156. import re
  157. import threading
  158. from tornado import escape
  159. from tornado.log import app_log
  160. from tornado.util import ObjectDict, exec_in, unicode_type
  161. from typing import Any, Union, Callable, List, Dict, Iterable, Optional, TextIO
  162. import typing
  163. if typing.TYPE_CHECKING:
  164. from typing import Tuple, ContextManager # noqa: F401
  165. _DEFAULT_AUTOESCAPE = "xhtml_escape"
  166. class _UnsetMarker:
  167. pass
  168. _UNSET = _UnsetMarker()
  169. def filter_whitespace(mode: str, text: str) -> str:
  170. """Transform whitespace in ``text`` according to ``mode``.
  171. Available modes are:
  172. * ``all``: Return all whitespace unmodified.
  173. * ``single``: Collapse consecutive whitespace with a single whitespace
  174. character, preserving newlines.
  175. * ``oneline``: Collapse all runs of whitespace into a single space
  176. character, removing all newlines in the process.
  177. .. versionadded:: 4.3
  178. """
  179. if mode == "all":
  180. return text
  181. elif mode == "single":
  182. text = re.sub(r"([\t ]+)", " ", text)
  183. text = re.sub(r"(\s*\n\s*)", "\n", text)
  184. return text
  185. elif mode == "oneline":
  186. return re.sub(r"(\s+)", " ", text)
  187. else:
  188. raise Exception("invalid whitespace mode %s" % mode)
  189. class Template:
  190. """A compiled template.
  191. We compile into Python from the given template_string. You can generate
  192. the template from variables with generate().
  193. """
  194. # note that the constructor's signature is not extracted with
  195. # autodoc because _UNSET looks like garbage. When changing
  196. # this signature update website/sphinx/template.rst too.
  197. def __init__(
  198. self,
  199. template_string: Union[str, bytes],
  200. name: str = "<string>",
  201. loader: Optional["BaseLoader"] = None,
  202. compress_whitespace: Union[bool, _UnsetMarker] = _UNSET,
  203. autoescape: Optional[Union[str, _UnsetMarker]] = _UNSET,
  204. whitespace: Optional[str] = None,
  205. ) -> None:
  206. """Construct a Template.
  207. :arg str template_string: the contents of the template file.
  208. :arg str name: the filename from which the template was loaded
  209. (used for error message).
  210. :arg tornado.template.BaseLoader loader: the `~tornado.template.BaseLoader` responsible
  211. for this template, used to resolve ``{% include %}`` and ``{% extend %}`` directives.
  212. :arg bool compress_whitespace: Deprecated since Tornado 4.3.
  213. Equivalent to ``whitespace="single"`` if true and
  214. ``whitespace="all"`` if false.
  215. :arg str autoescape: The name of a function in the template
  216. namespace, or ``None`` to disable escaping by default.
  217. :arg str whitespace: A string specifying treatment of whitespace;
  218. see `filter_whitespace` for options.
  219. .. versionchanged:: 4.3
  220. Added ``whitespace`` parameter; deprecated ``compress_whitespace``.
  221. """
  222. self.name = escape.native_str(name)
  223. if compress_whitespace is not _UNSET:
  224. # Convert deprecated compress_whitespace (bool) to whitespace (str).
  225. if whitespace is not None:
  226. raise Exception("cannot set both whitespace and compress_whitespace")
  227. whitespace = "single" if compress_whitespace else "all"
  228. if whitespace is None:
  229. if loader and loader.whitespace:
  230. whitespace = loader.whitespace
  231. else:
  232. # Whitespace defaults by filename.
  233. if name.endswith(".html") or name.endswith(".js"):
  234. whitespace = "single"
  235. else:
  236. whitespace = "all"
  237. # Validate the whitespace setting.
  238. assert whitespace is not None
  239. filter_whitespace(whitespace, "")
  240. if not isinstance(autoescape, _UnsetMarker):
  241. self.autoescape = autoescape # type: Optional[str]
  242. elif loader:
  243. self.autoescape = loader.autoescape
  244. else:
  245. self.autoescape = _DEFAULT_AUTOESCAPE
  246. self.namespace = loader.namespace if loader else {}
  247. reader = _TemplateReader(name, escape.native_str(template_string), whitespace)
  248. self.file = _File(self, _parse(reader, self))
  249. self.code = self._generate_python(loader)
  250. self.loader = loader
  251. try:
  252. # Under python2.5, the fake filename used here must match
  253. # the module name used in __name__ below.
  254. # The dont_inherit flag prevents template.py's future imports
  255. # from being applied to the generated code.
  256. self.compiled = compile(
  257. escape.to_unicode(self.code),
  258. "%s.generated.py" % self.name.replace(".", "_"),
  259. "exec",
  260. dont_inherit=True,
  261. )
  262. except Exception:
  263. formatted_code = _format_code(self.code).rstrip()
  264. app_log.error("%s code:\n%s", self.name, formatted_code)
  265. raise
  266. def generate(self, **kwargs: Any) -> bytes:
  267. """Generate this template with the given arguments."""
  268. namespace = {
  269. "escape": escape.xhtml_escape,
  270. "xhtml_escape": escape.xhtml_escape,
  271. "url_escape": escape.url_escape,
  272. "json_encode": escape.json_encode,
  273. "squeeze": escape.squeeze,
  274. "linkify": escape.linkify,
  275. "datetime": datetime,
  276. "_tt_utf8": escape.utf8, # for internal use
  277. "_tt_string_types": (unicode_type, bytes),
  278. # __name__ and __loader__ allow the traceback mechanism to find
  279. # the generated source code.
  280. "__name__": self.name.replace(".", "_"),
  281. "__loader__": ObjectDict(get_source=lambda name: self.code),
  282. }
  283. namespace.update(self.namespace)
  284. namespace.update(kwargs)
  285. exec_in(self.compiled, namespace)
  286. execute = typing.cast(Callable[[], bytes], namespace["_tt_execute"])
  287. # Clear the traceback module's cache of source data now that
  288. # we've generated a new template (mainly for this module's
  289. # unittests, where different tests reuse the same name).
  290. linecache.clearcache()
  291. return execute()
  292. def _generate_python(self, loader: Optional["BaseLoader"]) -> str:
  293. buffer = StringIO()
  294. try:
  295. # named_blocks maps from names to _NamedBlock objects
  296. named_blocks = {} # type: Dict[str, _NamedBlock]
  297. ancestors = self._get_ancestors(loader)
  298. ancestors.reverse()
  299. for ancestor in ancestors:
  300. ancestor.find_named_blocks(loader, named_blocks)
  301. writer = _CodeWriter(buffer, named_blocks, loader, ancestors[0].template)
  302. ancestors[0].generate(writer)
  303. return buffer.getvalue()
  304. finally:
  305. buffer.close()
  306. def _get_ancestors(self, loader: Optional["BaseLoader"]) -> List["_File"]:
  307. ancestors = [self.file]
  308. for chunk in self.file.body.chunks:
  309. if isinstance(chunk, _ExtendsBlock):
  310. if not loader:
  311. raise ParseError(
  312. "{% extends %} block found, but no " "template loader"
  313. )
  314. template = loader.load(chunk.name, self.name)
  315. ancestors.extend(template._get_ancestors(loader))
  316. return ancestors
  317. class BaseLoader:
  318. """Base class for template loaders.
  319. You must use a template loader to use template constructs like
  320. ``{% extends %}`` and ``{% include %}``. The loader caches all
  321. templates after they are loaded the first time.
  322. """
  323. def __init__(
  324. self,
  325. autoescape: Optional[str] = _DEFAULT_AUTOESCAPE,
  326. namespace: Optional[Dict[str, Any]] = None,
  327. whitespace: Optional[str] = None,
  328. ) -> None:
  329. """Construct a template loader.
  330. :arg str autoescape: The name of a function in the template
  331. namespace, such as "xhtml_escape", or ``None`` to disable
  332. autoescaping by default.
  333. :arg dict namespace: A dictionary to be added to the default template
  334. namespace, or ``None``.
  335. :arg str whitespace: A string specifying default behavior for
  336. whitespace in templates; see `filter_whitespace` for options.
  337. Default is "single" for files ending in ".html" and ".js" and
  338. "all" for other files.
  339. .. versionchanged:: 4.3
  340. Added ``whitespace`` parameter.
  341. """
  342. self.autoescape = autoescape
  343. self.namespace = namespace or {}
  344. self.whitespace = whitespace
  345. self.templates = {} # type: Dict[str, Template]
  346. # self.lock protects self.templates. It's a reentrant lock
  347. # because templates may load other templates via `include` or
  348. # `extends`. Note that thanks to the GIL this code would be safe
  349. # even without the lock, but could lead to wasted work as multiple
  350. # threads tried to compile the same template simultaneously.
  351. self.lock = threading.RLock()
  352. def reset(self) -> None:
  353. """Resets the cache of compiled templates."""
  354. with self.lock:
  355. self.templates = {}
  356. def resolve_path(self, name: str, parent_path: Optional[str] = None) -> str:
  357. """Converts a possibly-relative path to absolute (used internally)."""
  358. raise NotImplementedError()
  359. def load(self, name: str, parent_path: Optional[str] = None) -> Template:
  360. """Loads a template."""
  361. name = self.resolve_path(name, parent_path=parent_path)
  362. with self.lock:
  363. if name not in self.templates:
  364. self.templates[name] = self._create_template(name)
  365. return self.templates[name]
  366. def _create_template(self, name: str) -> Template:
  367. raise NotImplementedError()
  368. class Loader(BaseLoader):
  369. """A template loader that loads from a single root directory."""
  370. def __init__(self, root_directory: str, **kwargs: Any) -> None:
  371. super().__init__(**kwargs)
  372. self.root = os.path.abspath(root_directory)
  373. def resolve_path(self, name: str, parent_path: Optional[str] = None) -> str:
  374. if (
  375. parent_path
  376. and not parent_path.startswith("<")
  377. and not parent_path.startswith("/")
  378. and not name.startswith("/")
  379. ):
  380. current_path = os.path.join(self.root, parent_path)
  381. file_dir = os.path.dirname(os.path.abspath(current_path))
  382. relative_path = os.path.abspath(os.path.join(file_dir, name))
  383. if relative_path.startswith(self.root):
  384. name = relative_path[len(self.root) + 1 :]
  385. return name
  386. def _create_template(self, name: str) -> Template:
  387. path = os.path.join(self.root, name)
  388. with open(path, "rb") as f:
  389. template = Template(f.read(), name=name, loader=self)
  390. return template
  391. class DictLoader(BaseLoader):
  392. """A template loader that loads from a dictionary."""
  393. def __init__(self, dict: Dict[str, str], **kwargs: Any) -> None:
  394. super().__init__(**kwargs)
  395. self.dict = dict
  396. def resolve_path(self, name: str, parent_path: Optional[str] = None) -> str:
  397. if (
  398. parent_path
  399. and not parent_path.startswith("<")
  400. and not parent_path.startswith("/")
  401. and not name.startswith("/")
  402. ):
  403. file_dir = posixpath.dirname(parent_path)
  404. name = posixpath.normpath(posixpath.join(file_dir, name))
  405. return name
  406. def _create_template(self, name: str) -> Template:
  407. return Template(self.dict[name], name=name, loader=self)
  408. class _Node:
  409. def each_child(self) -> Iterable["_Node"]:
  410. return ()
  411. def generate(self, writer: "_CodeWriter") -> None:
  412. raise NotImplementedError()
  413. def find_named_blocks(
  414. self, loader: Optional[BaseLoader], named_blocks: Dict[str, "_NamedBlock"]
  415. ) -> None:
  416. for child in self.each_child():
  417. child.find_named_blocks(loader, named_blocks)
  418. class _File(_Node):
  419. def __init__(self, template: Template, body: "_ChunkList") -> None:
  420. self.template = template
  421. self.body = body
  422. self.line = 0
  423. def generate(self, writer: "_CodeWriter") -> None:
  424. writer.write_line("def _tt_execute():", self.line)
  425. with writer.indent():
  426. writer.write_line("_tt_buffer = []", self.line)
  427. writer.write_line("_tt_append = _tt_buffer.append", self.line)
  428. self.body.generate(writer)
  429. writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line)
  430. def each_child(self) -> Iterable["_Node"]:
  431. return (self.body,)
  432. class _ChunkList(_Node):
  433. def __init__(self, chunks: List[_Node]) -> None:
  434. self.chunks = chunks
  435. def generate(self, writer: "_CodeWriter") -> None:
  436. for chunk in self.chunks:
  437. chunk.generate(writer)
  438. def each_child(self) -> Iterable["_Node"]:
  439. return self.chunks
  440. class _NamedBlock(_Node):
  441. def __init__(self, name: str, body: _Node, template: Template, line: int) -> None:
  442. self.name = name
  443. self.body = body
  444. self.template = template
  445. self.line = line
  446. def each_child(self) -> Iterable["_Node"]:
  447. return (self.body,)
  448. def generate(self, writer: "_CodeWriter") -> None:
  449. block = writer.named_blocks[self.name]
  450. with writer.include(block.template, self.line):
  451. block.body.generate(writer)
  452. def find_named_blocks(
  453. self, loader: Optional[BaseLoader], named_blocks: Dict[str, "_NamedBlock"]
  454. ) -> None:
  455. named_blocks[self.name] = self
  456. _Node.find_named_blocks(self, loader, named_blocks)
  457. class _ExtendsBlock(_Node):
  458. def __init__(self, name: str) -> None:
  459. self.name = name
  460. class _IncludeBlock(_Node):
  461. def __init__(self, name: str, reader: "_TemplateReader", line: int) -> None:
  462. self.name = name
  463. self.template_name = reader.name
  464. self.line = line
  465. def find_named_blocks(
  466. self, loader: Optional[BaseLoader], named_blocks: Dict[str, _NamedBlock]
  467. ) -> None:
  468. assert loader is not None
  469. included = loader.load(self.name, self.template_name)
  470. included.file.find_named_blocks(loader, named_blocks)
  471. def generate(self, writer: "_CodeWriter") -> None:
  472. assert writer.loader is not None
  473. included = writer.loader.load(self.name, self.template_name)
  474. with writer.include(included, self.line):
  475. included.file.body.generate(writer)
  476. class _ApplyBlock(_Node):
  477. def __init__(self, method: str, line: int, body: _Node) -> None:
  478. self.method = method
  479. self.line = line
  480. self.body = body
  481. def each_child(self) -> Iterable["_Node"]:
  482. return (self.body,)
  483. def generate(self, writer: "_CodeWriter") -> None:
  484. method_name = "_tt_apply%d" % writer.apply_counter
  485. writer.apply_counter += 1
  486. writer.write_line("def %s():" % method_name, self.line)
  487. with writer.indent():
  488. writer.write_line("_tt_buffer = []", self.line)
  489. writer.write_line("_tt_append = _tt_buffer.append", self.line)
  490. self.body.generate(writer)
  491. writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line)
  492. writer.write_line(
  493. f"_tt_append(_tt_utf8({self.method}({method_name}())))", self.line
  494. )
  495. class _ControlBlock(_Node):
  496. def __init__(self, statement: str, line: int, body: _Node) -> None:
  497. self.statement = statement
  498. self.line = line
  499. self.body = body
  500. def each_child(self) -> Iterable[_Node]:
  501. return (self.body,)
  502. def generate(self, writer: "_CodeWriter") -> None:
  503. writer.write_line("%s:" % self.statement, self.line)
  504. with writer.indent():
  505. self.body.generate(writer)
  506. # Just in case the body was empty
  507. writer.write_line("pass", self.line)
  508. class _IntermediateControlBlock(_Node):
  509. def __init__(self, statement: str, line: int) -> None:
  510. self.statement = statement
  511. self.line = line
  512. def generate(self, writer: "_CodeWriter") -> None:
  513. # In case the previous block was empty
  514. writer.write_line("pass", self.line)
  515. writer.write_line("%s:" % self.statement, self.line, writer.indent_size() - 1)
  516. class _Statement(_Node):
  517. def __init__(self, statement: str, line: int) -> None:
  518. self.statement = statement
  519. self.line = line
  520. def generate(self, writer: "_CodeWriter") -> None:
  521. writer.write_line(self.statement, self.line)
  522. class _Expression(_Node):
  523. def __init__(self, expression: str, line: int, raw: bool = False) -> None:
  524. self.expression = expression
  525. self.line = line
  526. self.raw = raw
  527. def generate(self, writer: "_CodeWriter") -> None:
  528. writer.write_line("_tt_tmp = %s" % self.expression, self.line)
  529. writer.write_line(
  530. "if isinstance(_tt_tmp, _tt_string_types):" " _tt_tmp = _tt_utf8(_tt_tmp)",
  531. self.line,
  532. )
  533. writer.write_line("else: _tt_tmp = _tt_utf8(str(_tt_tmp))", self.line)
  534. if not self.raw and writer.current_template.autoescape is not None:
  535. # In python3 functions like xhtml_escape return unicode,
  536. # so we have to convert to utf8 again.
  537. writer.write_line(
  538. "_tt_tmp = _tt_utf8(%s(_tt_tmp))" % writer.current_template.autoescape,
  539. self.line,
  540. )
  541. writer.write_line("_tt_append(_tt_tmp)", self.line)
  542. class _Module(_Expression):
  543. def __init__(self, expression: str, line: int) -> None:
  544. super().__init__("_tt_modules." + expression, line, raw=True)
  545. class _Text(_Node):
  546. def __init__(self, value: str, line: int, whitespace: str) -> None:
  547. self.value = value
  548. self.line = line
  549. self.whitespace = whitespace
  550. def generate(self, writer: "_CodeWriter") -> None:
  551. value = self.value
  552. # Compress whitespace if requested, with a crude heuristic to avoid
  553. # altering preformatted whitespace.
  554. if "<pre>" not in value:
  555. value = filter_whitespace(self.whitespace, value)
  556. if value:
  557. writer.write_line("_tt_append(%r)" % escape.utf8(value), self.line)
  558. class ParseError(Exception):
  559. """Raised for template syntax errors.
  560. ``ParseError`` instances have ``filename`` and ``lineno`` attributes
  561. indicating the position of the error.
  562. .. versionchanged:: 4.3
  563. Added ``filename`` and ``lineno`` attributes.
  564. """
  565. def __init__(
  566. self, message: str, filename: Optional[str] = None, lineno: int = 0
  567. ) -> None:
  568. self.message = message
  569. # The names "filename" and "lineno" are chosen for consistency
  570. # with python SyntaxError.
  571. self.filename = filename
  572. self.lineno = lineno
  573. def __str__(self) -> str:
  574. return "%s at %s:%d" % (self.message, self.filename, self.lineno)
  575. class _CodeWriter:
  576. def __init__(
  577. self,
  578. file: TextIO,
  579. named_blocks: Dict[str, _NamedBlock],
  580. loader: Optional[BaseLoader],
  581. current_template: Template,
  582. ) -> None:
  583. self.file = file
  584. self.named_blocks = named_blocks
  585. self.loader = loader
  586. self.current_template = current_template
  587. self.apply_counter = 0
  588. self.include_stack = [] # type: List[Tuple[Template, int]]
  589. self._indent = 0
  590. def indent_size(self) -> int:
  591. return self._indent
  592. def indent(self) -> "ContextManager":
  593. class Indenter:
  594. def __enter__(_) -> "_CodeWriter":
  595. self._indent += 1
  596. return self
  597. def __exit__(_, *args: Any) -> None:
  598. assert self._indent > 0
  599. self._indent -= 1
  600. return Indenter()
  601. def include(self, template: Template, line: int) -> "ContextManager":
  602. self.include_stack.append((self.current_template, line))
  603. self.current_template = template
  604. class IncludeTemplate:
  605. def __enter__(_) -> "_CodeWriter":
  606. return self
  607. def __exit__(_, *args: Any) -> None:
  608. self.current_template = self.include_stack.pop()[0]
  609. return IncludeTemplate()
  610. def write_line(
  611. self, line: str, line_number: int, indent: Optional[int] = None
  612. ) -> None:
  613. if indent is None:
  614. indent = self._indent
  615. line_comment = " # %s:%d" % (self.current_template.name, line_number)
  616. if self.include_stack:
  617. ancestors = [
  618. "%s:%d" % (tmpl.name, lineno) for (tmpl, lineno) in self.include_stack
  619. ]
  620. line_comment += " (via %s)" % ", ".join(reversed(ancestors))
  621. print(" " * indent + line + line_comment, file=self.file)
  622. class _TemplateReader:
  623. def __init__(self, name: str, text: str, whitespace: str) -> None:
  624. self.name = name
  625. self.text = text
  626. self.whitespace = whitespace
  627. self.line = 1
  628. self.pos = 0
  629. def find(self, needle: str, start: int = 0, end: Optional[int] = None) -> int:
  630. assert start >= 0, start
  631. pos = self.pos
  632. start += pos
  633. if end is None:
  634. index = self.text.find(needle, start)
  635. else:
  636. end += pos
  637. assert end >= start
  638. index = self.text.find(needle, start, end)
  639. if index != -1:
  640. index -= pos
  641. return index
  642. def consume(self, count: Optional[int] = None) -> str:
  643. if count is None:
  644. count = len(self.text) - self.pos
  645. newpos = self.pos + count
  646. self.line += self.text.count("\n", self.pos, newpos)
  647. s = self.text[self.pos : newpos]
  648. self.pos = newpos
  649. return s
  650. def remaining(self) -> int:
  651. return len(self.text) - self.pos
  652. def __len__(self) -> int:
  653. return self.remaining()
  654. def __getitem__(self, key: Union[int, slice]) -> str:
  655. if isinstance(key, slice):
  656. size = len(self)
  657. start, stop, step = key.indices(size)
  658. if start is None:
  659. start = self.pos
  660. else:
  661. start += self.pos
  662. if stop is not None:
  663. stop += self.pos
  664. return self.text[slice(start, stop, step)]
  665. elif key < 0:
  666. return self.text[key]
  667. else:
  668. return self.text[self.pos + key]
  669. def __str__(self) -> str:
  670. return self.text[self.pos :]
  671. def raise_parse_error(self, msg: str) -> None:
  672. raise ParseError(msg, self.name, self.line)
  673. def _format_code(code: str) -> str:
  674. lines = code.splitlines()
  675. format = "%%%dd %%s\n" % len(repr(len(lines) + 1))
  676. return "".join([format % (i + 1, line) for (i, line) in enumerate(lines)])
  677. def _parse(
  678. reader: _TemplateReader,
  679. template: Template,
  680. in_block: Optional[str] = None,
  681. in_loop: Optional[str] = None,
  682. ) -> _ChunkList:
  683. body = _ChunkList([])
  684. while True:
  685. # Find next template directive
  686. curly = 0
  687. while True:
  688. curly = reader.find("{", curly)
  689. if curly == -1 or curly + 1 == reader.remaining():
  690. # EOF
  691. if in_block:
  692. reader.raise_parse_error(
  693. "Missing {%% end %%} block for %s" % in_block
  694. )
  695. body.chunks.append(
  696. _Text(reader.consume(), reader.line, reader.whitespace)
  697. )
  698. return body
  699. # If the first curly brace is not the start of a special token,
  700. # start searching from the character after it
  701. if reader[curly + 1] not in ("{", "%", "#"):
  702. curly += 1
  703. continue
  704. # When there are more than 2 curlies in a row, use the
  705. # innermost ones. This is useful when generating languages
  706. # like latex where curlies are also meaningful
  707. if (
  708. curly + 2 < reader.remaining()
  709. and reader[curly + 1] == "{"
  710. and reader[curly + 2] == "{"
  711. ):
  712. curly += 1
  713. continue
  714. break
  715. # Append any text before the special token
  716. if curly > 0:
  717. cons = reader.consume(curly)
  718. body.chunks.append(_Text(cons, reader.line, reader.whitespace))
  719. start_brace = reader.consume(2)
  720. line = reader.line
  721. # Template directives may be escaped as "{{!" or "{%!".
  722. # In this case output the braces and consume the "!".
  723. # This is especially useful in conjunction with jquery templates,
  724. # which also use double braces.
  725. if reader.remaining() and reader[0] == "!":
  726. reader.consume(1)
  727. body.chunks.append(_Text(start_brace, line, reader.whitespace))
  728. continue
  729. # Comment
  730. if start_brace == "{#":
  731. end = reader.find("#}")
  732. if end == -1:
  733. reader.raise_parse_error("Missing end comment #}")
  734. contents = reader.consume(end).strip()
  735. reader.consume(2)
  736. continue
  737. # Expression
  738. if start_brace == "{{":
  739. end = reader.find("}}")
  740. if end == -1:
  741. reader.raise_parse_error("Missing end expression }}")
  742. contents = reader.consume(end).strip()
  743. reader.consume(2)
  744. if not contents:
  745. reader.raise_parse_error("Empty expression")
  746. body.chunks.append(_Expression(contents, line))
  747. continue
  748. # Block
  749. assert start_brace == "{%", start_brace
  750. end = reader.find("%}")
  751. if end == -1:
  752. reader.raise_parse_error("Missing end block %}")
  753. contents = reader.consume(end).strip()
  754. reader.consume(2)
  755. if not contents:
  756. reader.raise_parse_error("Empty block tag ({% %})")
  757. operator, space, suffix = contents.partition(" ")
  758. suffix = suffix.strip()
  759. # Intermediate ("else", "elif", etc) blocks
  760. intermediate_blocks = {
  761. "else": {"if", "for", "while", "try"},
  762. "elif": {"if"},
  763. "except": {"try"},
  764. "finally": {"try"},
  765. }
  766. allowed_parents = intermediate_blocks.get(operator)
  767. if allowed_parents is not None:
  768. if not in_block:
  769. reader.raise_parse_error(f"{operator} outside {allowed_parents} block")
  770. if in_block not in allowed_parents:
  771. reader.raise_parse_error(
  772. f"{operator} block cannot be attached to {in_block} block"
  773. )
  774. body.chunks.append(_IntermediateControlBlock(contents, line))
  775. continue
  776. # End tag
  777. elif operator == "end":
  778. if not in_block:
  779. reader.raise_parse_error("Extra {% end %} block")
  780. return body
  781. elif operator in (
  782. "extends",
  783. "include",
  784. "set",
  785. "import",
  786. "from",
  787. "comment",
  788. "autoescape",
  789. "whitespace",
  790. "raw",
  791. "module",
  792. ):
  793. if operator == "comment":
  794. continue
  795. if operator == "extends":
  796. suffix = suffix.strip('"').strip("'")
  797. if not suffix:
  798. reader.raise_parse_error("extends missing file path")
  799. block = _ExtendsBlock(suffix) # type: _Node
  800. elif operator in ("import", "from"):
  801. if not suffix:
  802. reader.raise_parse_error("import missing statement")
  803. block = _Statement(contents, line)
  804. elif operator == "include":
  805. suffix = suffix.strip('"').strip("'")
  806. if not suffix:
  807. reader.raise_parse_error("include missing file path")
  808. block = _IncludeBlock(suffix, reader, line)
  809. elif operator == "set":
  810. if not suffix:
  811. reader.raise_parse_error("set missing statement")
  812. block = _Statement(suffix, line)
  813. elif operator == "autoescape":
  814. fn = suffix.strip() # type: Optional[str]
  815. if fn == "None":
  816. fn = None
  817. template.autoescape = fn
  818. continue
  819. elif operator == "whitespace":
  820. mode = suffix.strip()
  821. # Validate the selected mode
  822. filter_whitespace(mode, "")
  823. reader.whitespace = mode
  824. continue
  825. elif operator == "raw":
  826. block = _Expression(suffix, line, raw=True)
  827. elif operator == "module":
  828. block = _Module(suffix, line)
  829. body.chunks.append(block)
  830. continue
  831. elif operator in ("apply", "block", "try", "if", "for", "while"):
  832. # parse inner body recursively
  833. if operator in ("for", "while"):
  834. block_body = _parse(reader, template, operator, operator)
  835. elif operator == "apply":
  836. # apply creates a nested function so syntactically it's not
  837. # in the loop.
  838. block_body = _parse(reader, template, operator, None)
  839. else:
  840. block_body = _parse(reader, template, operator, in_loop)
  841. if operator == "apply":
  842. if not suffix:
  843. reader.raise_parse_error("apply missing method name")
  844. block = _ApplyBlock(suffix, line, block_body)
  845. elif operator == "block":
  846. if not suffix:
  847. reader.raise_parse_error("block missing name")
  848. block = _NamedBlock(suffix, block_body, template, line)
  849. else:
  850. block = _ControlBlock(contents, line, block_body)
  851. body.chunks.append(block)
  852. continue
  853. elif operator in ("break", "continue"):
  854. if not in_loop:
  855. reader.raise_parse_error(
  856. "{} outside {} block".format(operator, {"for", "while"})
  857. )
  858. body.chunks.append(_Statement(contents, line))
  859. continue
  860. else:
  861. reader.raise_parse_error("unknown operator: %r" % operator)