mirror of https://github.com/lukechilds/node.git
Browse Source
Pick up v8 inspector from [1]. This is the standalone version of the devtools debug protocol. [1] https://github.com/pavelfeldman/v8_inspector/commit/e1bb206 PR-URL: https://github.com/nodejs/node/pull/6792 Reviewed-By: jasnell - James M Snell <jasnell@gmail.com> Reviewed-By: addaleax - Anna Henningsen <anna@addaleax.net> Reviewed-By: bnoordhuis - Ben Noordhuis <info@bnoordhuis.nl>v7.x
256 changed files with 51764 additions and 0 deletions
@ -0,0 +1 @@ |
|||||
|
platform/v8_inspector/build/rjsmin.pyc |
@ -0,0 +1,2 @@ |
|||||
|
# v8_inspector |
||||
|
# v8_inspector |
@ -0,0 +1,10 @@ |
|||||
|
*.so |
||||
|
docs/_build |
||||
|
*.pyc |
||||
|
*.pyo |
||||
|
*.egg-info |
||||
|
*.egg |
||||
|
build |
||||
|
dist |
||||
|
.DS_Store |
||||
|
.tox |
@ -0,0 +1,24 @@ |
|||||
|
language: python |
||||
|
python: |
||||
|
- "2.6" |
||||
|
- "2.7" |
||||
|
- "3.3" |
||||
|
- "3.4" |
||||
|
- "3.5" |
||||
|
- "pypy" |
||||
|
|
||||
|
install: |
||||
|
- pip install tox |
||||
|
script: |
||||
|
- tox -e \ |
||||
|
$(echo $TRAVIS_PYTHON_VERSION | sed 's/^\([0-9]\)\.\([0-9]\).*/py\1\2/') |
||||
|
|
||||
|
notifications: |
||||
|
email: false |
||||
|
irc: |
||||
|
channels: |
||||
|
- "chat.freenode.net#pocoo" |
||||
|
on_success: change |
||||
|
on_failure: always |
||||
|
use_notice: true |
||||
|
skip_join: true |
@ -0,0 +1,33 @@ |
|||||
|
Jinja is written and maintained by the Jinja Team and various |
||||
|
contributors: |
||||
|
|
||||
|
Lead Developer: |
||||
|
|
||||
|
- Armin Ronacher <armin.ronacher@active-4.com> |
||||
|
|
||||
|
Developers: |
||||
|
|
||||
|
- Christoph Hack |
||||
|
- Georg Brandl |
||||
|
|
||||
|
Contributors: |
||||
|
|
||||
|
- Bryan McLemore |
||||
|
- Mickaël Guérin <kael@crocobox.org> |
||||
|
- Cameron Knight |
||||
|
- Lawrence Journal-World. |
||||
|
- David Cramer |
||||
|
|
||||
|
Patches and suggestions: |
||||
|
|
||||
|
- Ronny Pfannschmidt |
||||
|
- Axel Böhm |
||||
|
- Alexey Melchakov |
||||
|
- Bryan McLemore |
||||
|
- Clovis Fabricio (nosklo) |
||||
|
- Cameron Knight |
||||
|
- Peter van Dijk (Habbie) |
||||
|
- Stefan Ebner |
||||
|
- Rene Leonhardt |
||||
|
- Thomas Waldmann |
||||
|
- Cory Benfield (Lukasa) |
@ -0,0 +1,375 @@ |
|||||
|
Jinja2 Changelog |
||||
|
================ |
||||
|
|
||||
|
Version 2.8.1 |
||||
|
------------- |
||||
|
|
||||
|
(unreleased bugfix release) |
||||
|
|
||||
|
- Fixed the `for_qs` flag for `urlencode`. |
||||
|
|
||||
|
Version 2.8 |
||||
|
----------- |
||||
|
(codename Replacement, released on July 26th 2015) |
||||
|
|
||||
|
- Added `target` parameter to urlize function. |
||||
|
- Added support for `followsymlinks` to the file system loader. |
||||
|
- The truncate filter now counts the length. |
||||
|
- Added equalto filter that helps with select filters. |
||||
|
- Changed cache keys to use absolute file names if available |
||||
|
instead of load names. |
||||
|
- Fixed loop length calculation for some iterators. |
||||
|
- Changed how Jinja2 enforces strings to be native strings in |
||||
|
Python 2 to work when people break their default encoding. |
||||
|
- Added :func:`make_logging_undefined` which returns an undefined |
||||
|
object that logs failures into a logger. |
||||
|
- If unmarshalling of cached data fails the template will be |
||||
|
reloaded now. |
||||
|
- Implemented a block ``set`` tag. |
||||
|
- Default cache size was incrased to 400 from a low 50. |
||||
|
- Fixed ``is number`` test to accept long integers in all Python versions. |
||||
|
- Changed ``is number`` to accept Decimal as a number. |
||||
|
- Added a check for default arguments followed by non-default arguments. This |
||||
|
change makes ``{% macro m(x, y=1, z) %}...{% endmacro %}`` a syntax error. The |
||||
|
previous behavior for this code was broken anyway (resulting in the default |
||||
|
value being applied to `y`). |
||||
|
- Add ability to use custom subclasses of ``jinja2.compiler.CodeGenerator`` and |
||||
|
``jinja2.runtime.Context`` by adding two new attributes to the environment |
||||
|
(`code_generator_class` and `context_class`) (pull request ``#404``). |
||||
|
- added support for context/environment/evalctx decorator functions on |
||||
|
the finalize callback of the environment. |
||||
|
- escape query strings for urlencode properly. Previously slashes were not |
||||
|
escaped in that place. |
||||
|
- Add 'base' parameter to 'int' filter. |
||||
|
|
||||
|
Version 2.7.3 |
||||
|
------------- |
||||
|
(bugfix release, released on June 6th 2014) |
||||
|
|
||||
|
- Security issue: Corrected the security fix for the cache folder. This |
||||
|
fix was provided by RedHat. |
||||
|
|
||||
|
Version 2.7.2 |
||||
|
------------- |
||||
|
(bugfix release, released on January 10th 2014) |
||||
|
|
||||
|
- Prefix loader was not forwarding the locals properly to |
||||
|
inner loaders. This is now fixed. |
||||
|
- Security issue: Changed the default folder for the filesystem cache to be |
||||
|
user specific and read and write protected on UNIX systems. See `Debian bug |
||||
|
734747`_ for more information. |
||||
|
|
||||
|
.. _Debian bug 734747: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=734747 |
||||
|
|
||||
|
Version 2.7.1 |
||||
|
------------- |
||||
|
(bugfix release, released on August 7th 2013) |
||||
|
|
||||
|
- Fixed a bug with ``call_filter`` not working properly on environment |
||||
|
and context filters. |
||||
|
- Fixed lack of Python 3 support for bytecode caches. |
||||
|
- Reverted support for defining blocks in included templates as this |
||||
|
broke existing templates for users. |
||||
|
- Fixed some warnings with hashing of undefineds and nodes if Python |
||||
|
is run with warnings for Python 3. |
||||
|
- Added support for properly hashing undefined objects. |
||||
|
- Fixed a bug with the title filter not working on already uppercase |
||||
|
strings. |
||||
|
|
||||
|
Version 2.7 |
||||
|
----------- |
||||
|
(codename Translation, released on May 20th 2013) |
||||
|
|
||||
|
- Choice and prefix loaders now dispatch source and template lookup |
||||
|
separately in order to work in combination with module loaders as |
||||
|
advertised. |
||||
|
- Fixed filesizeformat. |
||||
|
- Added a non-silent option for babel extraction. |
||||
|
- Added `urlencode` filter that automatically quotes values for |
||||
|
URL safe usage with utf-8 as only supported encoding. If applications |
||||
|
want to change this encoding they can override the filter. |
||||
|
- Added `keep-trailing-newline` configuration to environments and |
||||
|
templates to optionally preserve the final trailing newline. |
||||
|
- Accessing `last` on the loop context no longer causes the iterator |
||||
|
to be consumed into a list. |
||||
|
- Python requirement changed: 2.6, 2.7 or >= 3.3 are required now, |
||||
|
supported by same source code, using the "six" compatibility library. |
||||
|
- Allow `contextfunction` and other decorators to be applied to `__call__`. |
||||
|
- Added support for changing from newline to different signs in the `wordwrap` |
||||
|
filter. |
||||
|
- Added support for ignoring memcache errors silently. |
||||
|
- Added support for keeping the trailing newline in templates. |
||||
|
- Added finer grained support for stripping whitespace on the left side |
||||
|
of blocks. |
||||
|
- Added `map`, `select`, `reject`, `selectattr` and `rejectattr` |
||||
|
filters. |
||||
|
- Added support for `loop.depth` to figure out how deep inside a recursive |
||||
|
loop the code is. |
||||
|
- Disabled py_compile for pypy and python 3. |
||||
|
|
||||
|
Version 2.6 |
||||
|
----------- |
||||
|
(codename Convolution, released on July 24th 2011) |
||||
|
|
||||
|
- internal attributes now raise an internal attribute error now instead |
||||
|
of returning an undefined. This fixes problems when passing undefined |
||||
|
objects to Python semantics expecting APIs. |
||||
|
- traceback support now works properly for PyPy. (Tested with 1.4) |
||||
|
- implemented operator intercepting for sandboxed environments. This |
||||
|
allows application developers to disable builtin operators for better |
||||
|
security. (For instance limit the mathematical operators to actual |
||||
|
integers instead of longs) |
||||
|
- groupby filter now supports dotted notation for grouping by attributes |
||||
|
of attributes. |
||||
|
- scoped blocks now properly treat toplevel assignments and imports. |
||||
|
Previously an import suddenly "disappeared" in a scoped block. |
||||
|
- automatically detect newer Python interpreter versions before loading code |
||||
|
from bytecode caches to prevent segfaults on invalid opcodes. The segfault |
||||
|
in earlier Jinja2 versions here was not a Jinja2 bug but a limitation in |
||||
|
the underlying Python interpreter. If you notice Jinja2 segfaulting in |
||||
|
earlier versions after an upgrade of the Python interpreter you don't have |
||||
|
to upgrade, it's enough to flush the bytecode cache. This just no longer |
||||
|
makes this necessary, Jinja2 will automatically detect these cases now. |
||||
|
- the sum filter can now sum up values by attribute. This is a backwards |
||||
|
incompatible change. The argument to the filter previously was the |
||||
|
optional starting index which defaultes to zero. This now became the |
||||
|
second argument to the function because it's rarely used. |
||||
|
- like sum, sort now also makes it possible to order items by attribute. |
||||
|
- like sum and sort, join now also is able to join attributes of objects |
||||
|
as string. |
||||
|
- the internal eval context now has a reference to the environment. |
||||
|
- added a mapping test to see if an object is a dict or an object with |
||||
|
a similar interface. |
||||
|
|
||||
|
Version 2.5.5 |
||||
|
------------- |
||||
|
(re-release of 2.5.4 with built documentation removed for filesize. |
||||
|
Released on October 18th 2010) |
||||
|
|
||||
|
- built documentation is no longer part of release. |
||||
|
|
||||
|
Version 2.5.4 |
||||
|
------------- |
||||
|
(bugfix release, released on October 17th 2010) |
||||
|
|
||||
|
- Fixed extensions not loading properly with overlays. |
||||
|
- Work around a bug in cpython for the debugger that causes segfaults |
||||
|
on 64bit big-endian architectures. |
||||
|
|
||||
|
Version 2.5.3 |
||||
|
------------- |
||||
|
(bugfix release, released on October 17th 2010) |
||||
|
|
||||
|
- fixed an operator precedence error introduced in 2.5.2. Statements |
||||
|
like "-foo.bar" had their implicit parentheses applied around the |
||||
|
first part of the expression ("(-foo).bar") instead of the more |
||||
|
correct "-(foo.bar)". |
||||
|
|
||||
|
Version 2.5.2 |
||||
|
------------- |
||||
|
(bugfix release, released on August 18th 2010) |
||||
|
|
||||
|
- improved setup.py script to better work with assumptions people |
||||
|
might still have from it (``--with-speedups``). |
||||
|
- fixed a packaging error that excluded the new debug support. |
||||
|
|
||||
|
Version 2.5.1 |
||||
|
------------- |
||||
|
(bugfix release, released on August 17th 2010) |
||||
|
|
||||
|
- StopIteration exceptions raised by functions called from templates |
||||
|
are now intercepted and converted to undefineds. This solves a |
||||
|
lot of debugging grief. (StopIteration is used internally to |
||||
|
abort template execution) |
||||
|
- improved performance of macro calls slightly. |
||||
|
- babel extraction can now properly extract newstyle gettext calls. |
||||
|
- using the variable `num` in newstyle gettext for something else |
||||
|
than the pluralize count will no longer raise a :exc:`KeyError`. |
||||
|
- removed builtin markup class and switched to markupsafe. For backwards |
||||
|
compatibility the pure Python implementation still exists but is |
||||
|
pulled from markupsafe by the Jinja2 developers. The debug support |
||||
|
went into a separate feature called "debugsupport" and is disabled |
||||
|
by default because it is only relevant for Python 2.4 |
||||
|
- fixed an issue with unary operators having the wrong precendence. |
||||
|
|
||||
|
Version 2.5 |
||||
|
----------- |
||||
|
(codename Incoherence, relased on May 29th 2010) |
||||
|
|
||||
|
- improved the sort filter (should have worked like this for a |
||||
|
long time) by adding support for case insensitive searches. |
||||
|
- fixed a bug for getattribute constant folding. |
||||
|
- support for newstyle gettext translations which result in a |
||||
|
nicer in-template user interface and more consistent |
||||
|
catalogs. (:ref:`newstyle-gettext`) |
||||
|
- it's now possible to register extensions after an environment |
||||
|
was created. |
||||
|
|
||||
|
Version 2.4.1 |
||||
|
------------- |
||||
|
(bugfix release, released on April 20th 2010) |
||||
|
|
||||
|
- fixed an error reporting bug for undefineds. |
||||
|
|
||||
|
Version 2.4 |
||||
|
----------- |
||||
|
(codename Correlation, released on April 13th 2010) |
||||
|
|
||||
|
- the environment template loading functions now transparently |
||||
|
pass through a template object if it was passed to it. This |
||||
|
makes it possible to import or extend from a template object |
||||
|
that was passed to the template. |
||||
|
- added a :class:`ModuleLoader` that can load templates from |
||||
|
precompiled sources. The environment now features a method |
||||
|
to compile the templates from a configured loader into a zip |
||||
|
file or folder. |
||||
|
- the _speedups C extension now supports Python 3. |
||||
|
- added support for autoescaping toggling sections and support |
||||
|
for evaluation contexts (:ref:`eval-context`). |
||||
|
- extensions have a priority now. |
||||
|
|
||||
|
Version 2.3.1 |
||||
|
------------- |
||||
|
(bugfix release, released on February 19th 2010) |
||||
|
|
||||
|
- fixed an error reporting bug on all python versions |
||||
|
- fixed an error reporting bug on Python 2.4 |
||||
|
|
||||
|
Version 2.3 |
||||
|
----------- |
||||
|
(3000 Pythons, released on February 10th 2010) |
||||
|
|
||||
|
- fixes issue with code generator that causes unbound variables |
||||
|
to be generated if set was used in if-blocks and other small |
||||
|
identifier problems. |
||||
|
- include tags are now able to select between multiple templates |
||||
|
and take the first that exists, if a list of templates is |
||||
|
given. |
||||
|
- fixed a problem with having call blocks in outer scopes that |
||||
|
have an argument that is also used as local variable in an |
||||
|
inner frame (#360). |
||||
|
- greatly improved error message reporting (#339) |
||||
|
- implicit tuple expressions can no longer be totally empty. |
||||
|
This change makes ``{% if %}...{% endif %}`` a syntax error |
||||
|
now. (#364) |
||||
|
- added support for translator comments if extracted via babel. |
||||
|
- added with-statement extension. |
||||
|
- experimental Python 3 support. |
||||
|
|
||||
|
Version 2.2.1 |
||||
|
------------- |
||||
|
(bugfix release, released on September 14th 2009) |
||||
|
|
||||
|
- fixes some smaller problems for Jinja2 on Jython. |
||||
|
|
||||
|
Version 2.2 |
||||
|
----------- |
||||
|
(codename Kong, released on September 13th 2009) |
||||
|
|
||||
|
- Include statements can now be marked with ``ignore missing`` to skip |
||||
|
non existing templates. |
||||
|
- Priority of `not` raised. It's now possible to write `not foo in bar` |
||||
|
as an alias to `foo not in bar` like in python. Previously the grammar |
||||
|
required parentheses (`not (foo in bar)`) which was odd. |
||||
|
- Fixed a bug that caused syntax errors when defining macros or using the |
||||
|
`{% call %}` tag inside loops. |
||||
|
- Fixed a bug in the parser that made ``{{ foo[1, 2] }}`` impossible. |
||||
|
- Made it possible to refer to names from outer scopes in included templates |
||||
|
that were unused in the callers frame (#327) |
||||
|
- Fixed a bug that caused internal errors if names where used as iteration |
||||
|
variable and regular variable *after* the loop if that variable was unused |
||||
|
*before* the loop. (#331) |
||||
|
- Added support for optional `scoped` modifier to blocks. |
||||
|
- Added support for line-comments. |
||||
|
- Added the `meta` module. |
||||
|
- Renamed (undocumented) attribute "overlay" to "overlayed" on the |
||||
|
environment because it was clashing with a method of the same name. |
||||
|
- speedup extension is now disabled by default. |
||||
|
|
||||
|
Version 2.1.1 |
||||
|
------------- |
||||
|
(Bugfix release) |
||||
|
|
||||
|
- Fixed a translation error caused by looping over empty recursive loops. |
||||
|
|
||||
|
Version 2.1 |
||||
|
----------- |
||||
|
(codename Yasuzō, released on November 23rd 2008) |
||||
|
|
||||
|
- fixed a bug with nested loops and the special loop variable. Before the |
||||
|
change an inner loop overwrote the loop variable from the outer one after |
||||
|
iteration. |
||||
|
|
||||
|
- fixed a bug with the i18n extension that caused the explicit pluralization |
||||
|
block to look up the wrong variable. |
||||
|
|
||||
|
- fixed a limitation in the lexer that made ``{{ foo.0.0 }}`` impossible. |
||||
|
|
||||
|
- index based subscribing of variables with a constant value returns an |
||||
|
undefined object now instead of raising an index error. This was a bug |
||||
|
caused by eager optimizing. |
||||
|
|
||||
|
- the i18n extension looks up `foo.ugettext` now followed by `foo.gettext` |
||||
|
if an translations object is installed. This makes dealing with custom |
||||
|
translations classes easier. |
||||
|
|
||||
|
- fixed a confusing behavior with conditional extending. loops were partially |
||||
|
executed under some conditions even though they were not part of a visible |
||||
|
area. |
||||
|
|
||||
|
- added `sort` filter that works like `dictsort` but for arbitrary sequences. |
||||
|
|
||||
|
- fixed a bug with empty statements in macros. |
||||
|
|
||||
|
- implemented a bytecode cache system. (:ref:`bytecode-cache`) |
||||
|
|
||||
|
- the template context is now weakref-able |
||||
|
|
||||
|
- inclusions and imports "with context" forward all variables now, not only |
||||
|
the initial context. |
||||
|
|
||||
|
- added a cycle helper called `cycler`. |
||||
|
|
||||
|
- added a joining helper called `joiner`. |
||||
|
|
||||
|
- added a `compile_expression` method to the environment that allows compiling |
||||
|
of Jinja expressions into callable Python objects. |
||||
|
|
||||
|
- fixed an escaping bug in urlize |
||||
|
|
||||
|
Version 2.0 |
||||
|
----------- |
||||
|
(codename jinjavitus, released on July 17th 2008) |
||||
|
|
||||
|
- the subscribing of objects (looking up attributes and items) changed from |
||||
|
slightly. It's now possible to give attributes or items a higher priority |
||||
|
by either using dot-notation lookup or the bracket syntax. This also |
||||
|
changed the AST slightly. `Subscript` is gone and was replaced with |
||||
|
:class:`~jinja2.nodes.Getitem` and :class:`~jinja2.nodes.Getattr`. |
||||
|
|
||||
|
For more information see :ref:`the implementation details <notes-on-subscriptions>`. |
||||
|
|
||||
|
- added support for preprocessing and token stream filtering for extensions. |
||||
|
This would allow extensions to allow simplified gettext calls in template |
||||
|
data and something similar. |
||||
|
|
||||
|
- added :meth:`jinja2.environment.TemplateStream.dump`. |
||||
|
|
||||
|
- added missing support for implicit string literal concatenation. |
||||
|
``{{ "foo" "bar" }}`` is equivalent to ``{{ "foobar" }}`` |
||||
|
|
||||
|
- `else` is optional for conditional expressions. If not given it evaluates |
||||
|
to `false`. |
||||
|
|
||||
|
- improved error reporting for undefined values by providing a position. |
||||
|
|
||||
|
- `filesizeformat` filter uses decimal prefixes now per default and can be |
||||
|
set to binary mode with the second parameter. |
||||
|
|
||||
|
- fixed bug in finalizer |
||||
|
|
||||
|
Version 2.0rc1 |
||||
|
-------------- |
||||
|
(no codename, released on June 9th 2008) |
||||
|
|
||||
|
- first release of Jinja2 |
@ -0,0 +1,31 @@ |
|||||
|
Copyright (c) 2009 by the Jinja Team, see AUTHORS for more details. |
||||
|
|
||||
|
Some rights reserved. |
||||
|
|
||||
|
Redistribution and use in source and binary forms, with or without |
||||
|
modification, are permitted provided that the following conditions are |
||||
|
met: |
||||
|
|
||||
|
* Redistributions of source code must retain the above copyright |
||||
|
notice, this list of conditions and the following disclaimer. |
||||
|
|
||||
|
* Redistributions in binary form must reproduce the above |
||||
|
copyright notice, this list of conditions and the following |
||||
|
disclaimer in the documentation and/or other materials provided |
||||
|
with the distribution. |
||||
|
|
||||
|
* The names of the contributors may not be used to endorse or |
||||
|
promote products derived from this software without specific |
||||
|
prior written permission. |
||||
|
|
||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,12 @@ |
|||||
|
include MANIFEST.in Makefile CHANGES LICENSE AUTHORS run-tests.py |
||||
|
recursive-include docs * |
||||
|
recursive-include custom_fixers * |
||||
|
recursive-include ext * |
||||
|
recursive-include artwork * |
||||
|
recursive-include examples * |
||||
|
recursive-include jinja2/testsuite/res * |
||||
|
recursive-exclude docs/_build * |
||||
|
recursive-exclude jinja2 *.pyc |
||||
|
recursive-exclude docs *.pyc |
||||
|
recursive-exclude jinja2 *.pyo |
||||
|
recursive-exclude docs *.pyo |
@ -0,0 +1,21 @@ |
|||||
|
test: |
||||
|
py.test |
||||
|
|
||||
|
develop: |
||||
|
pip install --editable . |
||||
|
|
||||
|
tox-test: |
||||
|
@tox |
||||
|
|
||||
|
release: |
||||
|
python scripts/make-release.py |
||||
|
|
||||
|
upload-docs: |
||||
|
$(MAKE) -C docs html dirhtml latex |
||||
|
$(MAKE) -C docs/_build/latex all-pdf |
||||
|
cd docs/_build/; mv html jinja-docs; zip -r jinja-docs.zip jinja-docs; mv jinja-docs html |
||||
|
scp -r docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/jinja.pocoo.org/docs/ |
||||
|
scp -r docs/_build/latex/Jinja2.pdf flow.srv.pocoo.org:/srv/websites/jinja.pocoo.org/docs/jinja-docs.pdf |
||||
|
scp -r docs/_build/jinja-docs.zip flow.srv.pocoo.org:/srv/websites/jinja.pocoo.org/docs/ |
||||
|
|
||||
|
.PHONY: test |
@ -0,0 +1,49 @@ |
|||||
|
Jinja2 |
||||
|
~~~~~~ |
||||
|
|
||||
|
Jinja2 is a template engine written in pure Python. It provides a |
||||
|
`Django`_ inspired non-XML syntax but supports inline expressions and |
||||
|
an optional `sandboxed`_ environment. |
||||
|
|
||||
|
Nutshell |
||||
|
-------- |
||||
|
|
||||
|
Here a small example of a Jinja template:: |
||||
|
|
||||
|
{% extends 'base.html' %} |
||||
|
{% block title %}Memberlist{% endblock %} |
||||
|
{% block content %} |
||||
|
<ul> |
||||
|
{% for user in users %} |
||||
|
<li><a href="{{ user.url }}">{{ user.username }}</a></li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
{% endblock %} |
||||
|
|
||||
|
Philosophy |
||||
|
---------- |
||||
|
|
||||
|
Application logic is for the controller, but don't make the template designer's |
||||
|
life difficult by restricting functionality too much. |
||||
|
|
||||
|
For more information visit the new `Jinja2 webpage`_ and `documentation`_. |
||||
|
|
||||
|
The `Jinja2 tip`_ is installable via `easy_install` with ``easy_install |
||||
|
Jinja2==dev``. |
||||
|
|
||||
|
.. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security) |
||||
|
.. _Django: http://www.djangoproject.com/ |
||||
|
.. _Jinja2 webpage: http://jinja.pocoo.org/ |
||||
|
.. _documentation: http://jinja.pocoo.org/docs/ |
||||
|
.. _Jinja2 tip: http://jinja.pocoo.org/docs/intro/#as-a-python-egg-via-easy-install |
||||
|
|
||||
|
Builds |
||||
|
------ |
||||
|
|
||||
|
+---------------------+------------------------------------------------------------------------------+ |
||||
|
| ``master`` | .. image:: https://travis-ci.org/mitsuhiko/jinja2.svg?branch=master | |
||||
|
| | :target: https://travis-ci.org/mitsuhiko/jinja2 | |
||||
|
+---------------------+------------------------------------------------------------------------------+ |
||||
|
| ``2.7-maintenance`` | .. image:: https://travis-ci.org/mitsuhiko/jinja2.svg?branch=2.7-maintenance | |
||||
|
| | :target: https://travis-ci.org/mitsuhiko/jinja2 | |
||||
|
+---------------------+------------------------------------------------------------------------------+ |
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,118 @@ |
|||||
|
# Makefile for Sphinx documentation
|
||||
|
#
|
||||
|
|
||||
|
# You can set these variables from the command line.
|
||||
|
SPHINXOPTS = |
||||
|
SPHINXBUILD = sphinx-build |
||||
|
PAPER = |
||||
|
BUILDDIR = _build |
||||
|
|
||||
|
# Internal variables.
|
||||
|
PAPEROPT_a4 = -D latex_paper_size=a4 |
||||
|
PAPEROPT_letter = -D latex_paper_size=letter |
||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . |
||||
|
|
||||
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp epub latex changes linkcheck doctest |
||||
|
|
||||
|
help: |
||||
|
@echo "Please use \`make <target>' where <target> is one of" |
||||
|
@echo " html to make standalone HTML files" |
||||
|
@echo " dirhtml to make HTML files named index.html in directories" |
||||
|
@echo " singlehtml to make a single large HTML file" |
||||
|
@echo " pickle to make pickle files" |
||||
|
@echo " json to make JSON files" |
||||
|
@echo " htmlhelp to make HTML files and a HTML help project" |
||||
|
@echo " qthelp to make HTML files and a qthelp project" |
||||
|
@echo " devhelp to make HTML files and a Devhelp project" |
||||
|
@echo " epub to make an epub" |
||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" |
||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex" |
||||
|
@echo " changes to make an overview of all changed/added/deprecated items" |
||||
|
@echo " linkcheck to check all external links for integrity" |
||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)" |
||||
|
|
||||
|
clean: |
||||
|
-rm -rf $(BUILDDIR)/* |
||||
|
|
||||
|
html: |
||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html |
||||
|
@echo |
||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." |
||||
|
|
||||
|
dirhtml: |
||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml |
||||
|
@echo |
||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." |
||||
|
|
||||
|
singlehtml: |
||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml |
||||
|
@echo |
||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." |
||||
|
|
||||
|
pickle: |
||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle |
||||
|
@echo |
||||
|
@echo "Build finished; now you can process the pickle files." |
||||
|
|
||||
|
json: |
||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json |
||||
|
@echo |
||||
|
@echo "Build finished; now you can process the JSON files." |
||||
|
|
||||
|
htmlhelp: |
||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp |
||||
|
@echo |
||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
|
".hhp project file in $(BUILDDIR)/htmlhelp." |
||||
|
|
||||
|
qthelp: |
||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp |
||||
|
@echo |
||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:" |
||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask.qhcp" |
||||
|
@echo "To view the help file:" |
||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask.qhc" |
||||
|
|
||||
|
devhelp: |
||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) _build/devhelp |
||||
|
@echo |
||||
|
@echo "Build finished." |
||||
|
@echo "To view the help file:" |
||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/Flask" |
||||
|
@echo "# ln -s _build/devhelp $$HOME/.local/share/devhelp/Flask" |
||||
|
@echo "# devhelp" |
||||
|
|
||||
|
epub: |
||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub |
||||
|
@echo |
||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub." |
||||
|
|
||||
|
latex: |
||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex |
||||
|
@echo |
||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." |
||||
|
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
|
"run these through (pdf)latex." |
||||
|
|
||||
|
latexpdf: latex |
||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex |
||||
|
@echo "Running LaTeX files through pdflatex..." |
||||
|
make -C _build/latex all-pdf |
||||
|
@echo "pdflatex finished; the PDF files are in _build/latex." |
||||
|
|
||||
|
changes: |
||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes |
||||
|
@echo |
||||
|
@echo "The overview file is in $(BUILDDIR)/changes." |
||||
|
|
||||
|
linkcheck: |
||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck |
||||
|
@echo |
||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||
|
"or in $(BUILDDIR)/linkcheck/output.txt." |
||||
|
|
||||
|
doctest: |
||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest |
||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
|
"results in $(BUILDDIR)/doctest/output.txt." |
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1,20 @@ |
|||||
|
<h3>About Jinja2</h3> |
||||
|
<p> |
||||
|
Jinja2 is a full featured template engine for Python. It has full unicode |
||||
|
support, an optional integrated sandboxed execution environment, widely used |
||||
|
and BSD licensed. |
||||
|
</p> |
||||
|
<h3>Other Formats</h3> |
||||
|
<p> |
||||
|
You can download the documentation in other formats as well: |
||||
|
</p> |
||||
|
<ul> |
||||
|
<li><a href="http://jinja.pocoo.org/docs/jinja-docs.pdf">as PDF</a> |
||||
|
<li><a href="http://jinja.pocoo.org/docs/jinja-docs.zip">as zipped HTML</a> |
||||
|
</ul> |
||||
|
<h3>Useful Links</h3> |
||||
|
<ul> |
||||
|
<li><a href="http://jinja.pocoo.org/">The Jinja2 Website</a></li> |
||||
|
<li><a href="http://pypi.python.org/pypi/Jinja2">Jinja2 @ PyPI</a></li> |
||||
|
<li><a href="http://github.com/mitsuhiko/jinja2">Jinja2 @ github</a></li> |
||||
|
</ul> |
@ -0,0 +1,3 @@ |
|||||
|
<p class="logo"><a href="{{ pathto(master_doc) }}"> |
||||
|
<img class="logo" src="{{ pathto('_static/jinja-small.png', 1) }}" alt="Logo"/> |
||||
|
</a></p> |
@ -0,0 +1,37 @@ |
|||||
|
Copyright (c) 2010 by Armin Ronacher. |
||||
|
|
||||
|
Some rights reserved. |
||||
|
|
||||
|
Redistribution and use in source and binary forms of the theme, with or |
||||
|
without modification, are permitted provided that the following conditions |
||||
|
are met: |
||||
|
|
||||
|
* Redistributions of source code must retain the above copyright |
||||
|
notice, this list of conditions and the following disclaimer. |
||||
|
|
||||
|
* Redistributions in binary form must reproduce the above |
||||
|
copyright notice, this list of conditions and the following |
||||
|
disclaimer in the documentation and/or other materials provided |
||||
|
with the distribution. |
||||
|
|
||||
|
* The names of the contributors may not be used to endorse or |
||||
|
promote products derived from this software without specific |
||||
|
prior written permission. |
||||
|
|
||||
|
We kindly ask you to only use these themes in an unmodified manner just |
||||
|
for Flask and Flask-related products, not for unrelated projects. If you |
||||
|
like the visual style and want to use it for your own projects, please |
||||
|
consider making some larger changes to the themes (such as changing |
||||
|
font faces, sizes, colors or margins). |
||||
|
|
||||
|
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE |
||||
|
POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,31 @@ |
|||||
|
Flask Sphinx Styles |
||||
|
=================== |
||||
|
|
||||
|
This repository contains sphinx styles for Flask and Flask related |
||||
|
projects. To use this style in your Sphinx documentation, follow |
||||
|
this guide: |
||||
|
|
||||
|
1. put this folder as _themes into your docs folder. Alternatively |
||||
|
you can also use git submodules to check out the contents there. |
||||
|
2. add this to your conf.py: |
||||
|
|
||||
|
sys.path.append(os.path.abspath('_themes')) |
||||
|
html_theme_path = ['_themes'] |
||||
|
html_theme = 'flask' |
||||
|
|
||||
|
The following themes exist: |
||||
|
|
||||
|
- 'flask' - the standard flask documentation theme for large |
||||
|
projects |
||||
|
- 'flask_small' - small one-page theme. Intended to be used by |
||||
|
very small addon libraries for flask. |
||||
|
|
||||
|
The following options exist for the flask_small theme: |
||||
|
|
||||
|
[options] |
||||
|
index_logo = '' filename of a picture in _static |
||||
|
to be used as replacement for the |
||||
|
h1 in the index.rst file. |
||||
|
index_logo_height = 120px height of the index logo |
||||
|
github_fork = '' repository name on github for the |
||||
|
"fork me" badge |
@ -0,0 +1,8 @@ |
|||||
|
{%- extends "basic/layout.html" %} |
||||
|
{%- block relbar2 %}{% endblock %} |
||||
|
{%- block footer %} |
||||
|
<div class="footer"> |
||||
|
© Copyright {{ copyright }}. |
||||
|
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>. |
||||
|
</div> |
||||
|
{%- endblock %} |
@ -0,0 +1,19 @@ |
|||||
|
<h3>Related Topics</h3> |
||||
|
<ul> |
||||
|
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul> |
||||
|
{%- for parent in parents %} |
||||
|
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul> |
||||
|
{%- endfor %} |
||||
|
{%- if prev %} |
||||
|
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter') |
||||
|
}}">{{ prev.title }}</a></li> |
||||
|
{%- endif %} |
||||
|
{%- if next %} |
||||
|
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter') |
||||
|
}}">{{ next.title }}</a></li> |
||||
|
{%- endif %} |
||||
|
{%- for parent in parents %} |
||||
|
</ul></li> |
||||
|
{%- endfor %} |
||||
|
</ul></li> |
||||
|
</ul> |
@ -0,0 +1,396 @@ |
|||||
|
/* |
||||
|
* jinja.css_t |
||||
|
* ~~~~~~~~~~~ |
||||
|
* |
||||
|
* :copyright: Copyright 2011 by Armin Ronacher. |
||||
|
* :license: Flask Design License, see LICENSE for details. |
||||
|
*/ |
||||
|
|
||||
|
@import url(http://fonts.googleapis.com/css?family=Crimson+Text); |
||||
|
|
||||
|
{% set page_width = '940px' %} |
||||
|
{% set sidebar_width = '220px' %} |
||||
|
{% set font_family = 'Georgia, serif' %} |
||||
|
{% set header_font_family = 'Crimson Text, ' ~ font_family %} |
||||
|
|
||||
|
@import url("basic.css"); |
||||
|
|
||||
|
/* -- page layout ----------------------------------------------------------- */ |
||||
|
|
||||
|
body { |
||||
|
font-family: {{ font_family }}; |
||||
|
font-size: 17px; |
||||
|
background-color: white; |
||||
|
color: #000; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
div.document { |
||||
|
width: {{ page_width }}; |
||||
|
margin: 30px auto 0 auto; |
||||
|
} |
||||
|
|
||||
|
div.documentwrapper { |
||||
|
float: left; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
div.bodywrapper { |
||||
|
margin: 0 0 0 {{ sidebar_width }}; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar { |
||||
|
width: {{ sidebar_width }}; |
||||
|
} |
||||
|
|
||||
|
hr { |
||||
|
border: 1px solid #B1B4B6; |
||||
|
} |
||||
|
|
||||
|
div.body { |
||||
|
background-color: #ffffff; |
||||
|
color: #3E4349; |
||||
|
padding: 0 30px 0 30px; |
||||
|
} |
||||
|
|
||||
|
img.floatingflask { |
||||
|
padding: 0 0 10px 10px; |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
div.footer { |
||||
|
width: {{ page_width }}; |
||||
|
margin: 20px auto 30px auto; |
||||
|
font-size: 14px; |
||||
|
color: #888; |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
div.footer a { |
||||
|
color: #888; |
||||
|
} |
||||
|
|
||||
|
div.related { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar a { |
||||
|
color: #444; |
||||
|
text-decoration: none; |
||||
|
border-bottom: 1px dotted #999; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar a:hover { |
||||
|
border-bottom: 1px solid #999; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar { |
||||
|
font-size: 15px; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebarwrapper { |
||||
|
padding: 18px 10px; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebarwrapper p.logo { |
||||
|
padding: 0 0 20px 0; |
||||
|
margin: 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar h3, |
||||
|
div.sphinxsidebar h4 { |
||||
|
font-family: {{ font_family }}; |
||||
|
color: #444; |
||||
|
font-size: 24px; |
||||
|
font-weight: normal; |
||||
|
margin: 0 0 5px 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar h4 { |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar h3 a { |
||||
|
color: #444; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar p.logo a, |
||||
|
div.sphinxsidebar h3 a, |
||||
|
div.sphinxsidebar p.logo a:hover, |
||||
|
div.sphinxsidebar h3 a:hover { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar p { |
||||
|
color: #555; |
||||
|
margin: 10px 0; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar ul { |
||||
|
margin: 10px 0; |
||||
|
padding: 0; |
||||
|
color: #000; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar input { |
||||
|
border: 1px solid #ccc; |
||||
|
font-family: {{ font_family }}; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
div.sphinxsidebar form.search input[name="q"] { |
||||
|
width: 130px; |
||||
|
} |
||||
|
|
||||
|
/* -- body styles ----------------------------------------------------------- */ |
||||
|
|
||||
|
a { |
||||
|
color: #aa0000; |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
a:hover { |
||||
|
color: #dd0000; |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
div.body h1, |
||||
|
div.body h2, |
||||
|
div.body h3, |
||||
|
div.body h4, |
||||
|
div.body h5, |
||||
|
div.body h6 { |
||||
|
font-family: {{ header_font_family }}; |
||||
|
font-weight: normal; |
||||
|
margin: 30px 0px 10px 0px; |
||||
|
padding: 0; |
||||
|
color: black; |
||||
|
} |
||||
|
|
||||
|
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } |
||||
|
div.body h2 { font-size: 180%; } |
||||
|
div.body h3 { font-size: 150%; } |
||||
|
div.body h4 { font-size: 130%; } |
||||
|
div.body h5 { font-size: 100%; } |
||||
|
div.body h6 { font-size: 100%; } |
||||
|
|
||||
|
a.headerlink { |
||||
|
color: #ddd; |
||||
|
padding: 0 4px; |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
|
||||
|
a.headerlink:hover { |
||||
|
color: #444; |
||||
|
background: #eaeaea; |
||||
|
} |
||||
|
|
||||
|
div.body p, div.body dd, div.body li { |
||||
|
line-height: 1.4em; |
||||
|
} |
||||
|
|
||||
|
div.admonition { |
||||
|
background: #fafafa; |
||||
|
margin: 20px -30px; |
||||
|
padding: 10px 30px; |
||||
|
border-top: 1px solid #ccc; |
||||
|
border-bottom: 1px solid #ccc; |
||||
|
} |
||||
|
|
||||
|
div.admonition tt.xref, div.admonition a tt { |
||||
|
border-bottom: 1px solid #fafafa; |
||||
|
} |
||||
|
|
||||
|
dd div.admonition { |
||||
|
margin-left: -60px; |
||||
|
padding-left: 60px; |
||||
|
} |
||||
|
|
||||
|
div.admonition p.admonition-title { |
||||
|
font-family: {{ font_family }}; |
||||
|
font-weight: normal; |
||||
|
font-size: 24px; |
||||
|
margin: 0 0 10px 0; |
||||
|
padding: 0; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
div.admonition p.last { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
div.highlight { |
||||
|
background-color: white; |
||||
|
} |
||||
|
|
||||
|
dt:target, .highlight { |
||||
|
background: #FAF3E8; |
||||
|
} |
||||
|
|
||||
|
div.note { |
||||
|
background-color: #eee; |
||||
|
border: 1px solid #ccc; |
||||
|
} |
||||
|
|
||||
|
div.seealso { |
||||
|
background-color: #ffc; |
||||
|
border: 1px solid #ff6; |
||||
|
} |
||||
|
|
||||
|
div.topic { |
||||
|
background-color: #eee; |
||||
|
} |
||||
|
|
||||
|
p.admonition-title { |
||||
|
display: inline; |
||||
|
} |
||||
|
|
||||
|
p.admonition-title:after { |
||||
|
content: ":"; |
||||
|
} |
||||
|
|
||||
|
pre, tt { |
||||
|
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; |
||||
|
font-size: 0.85em; |
||||
|
} |
||||
|
|
||||
|
img.screenshot { |
||||
|
} |
||||
|
|
||||
|
tt.descname, tt.descclassname { |
||||
|
font-size: 0.95em; |
||||
|
} |
||||
|
|
||||
|
tt.descname { |
||||
|
padding-right: 0.08em; |
||||
|
} |
||||
|
|
||||
|
img.screenshot { |
||||
|
-moz-box-shadow: 2px 2px 4px #eee; |
||||
|
-webkit-box-shadow: 2px 2px 4px #eee; |
||||
|
box-shadow: 2px 2px 4px #eee; |
||||
|
} |
||||
|
|
||||
|
table.docutils { |
||||
|
border: 1px solid #888; |
||||
|
-moz-box-shadow: 2px 2px 4px #eee; |
||||
|
-webkit-box-shadow: 2px 2px 4px #eee; |
||||
|
box-shadow: 2px 2px 4px #eee; |
||||
|
} |
||||
|
|
||||
|
table.docutils td, table.docutils th { |
||||
|
border: 1px solid #888; |
||||
|
padding: 0.25em 0.7em; |
||||
|
} |
||||
|
|
||||
|
table.field-list, table.footnote { |
||||
|
border: none; |
||||
|
-moz-box-shadow: none; |
||||
|
-webkit-box-shadow: none; |
||||
|
box-shadow: none; |
||||
|
} |
||||
|
|
||||
|
table.footnote { |
||||
|
margin: 15px 0; |
||||
|
width: 100%; |
||||
|
border: 1px solid #eee; |
||||
|
background: #fdfdfd; |
||||
|
font-size: 0.9em; |
||||
|
} |
||||
|
|
||||
|
table.footnote + table.footnote { |
||||
|
margin-top: -15px; |
||||
|
border-top: none; |
||||
|
} |
||||
|
|
||||
|
table.field-list th { |
||||
|
padding: 0 0.8em 0 0; |
||||
|
} |
||||
|
|
||||
|
table.field-list td { |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
table.footnote td.label { |
||||
|
width: 0px; |
||||
|
padding: 0.3em 0 0.3em 0.5em; |
||||
|
} |
||||
|
|
||||
|
table.footnote td { |
||||
|
padding: 0.3em 0.5em; |
||||
|
} |
||||
|
|
||||
|
dl { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
dl dd { |
||||
|
margin-left: 30px; |
||||
|
} |
||||
|
|
||||
|
blockquote { |
||||
|
margin: 0 0 0 30px; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
ul, ol { |
||||
|
margin: 10px 0 10px 30px; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
pre { |
||||
|
background: #eee; |
||||
|
padding: 7px 30px; |
||||
|
margin: 15px -30px; |
||||
|
line-height: 1.3em; |
||||
|
} |
||||
|
|
||||
|
dl pre, blockquote pre, li pre { |
||||
|
margin-left: -60px; |
||||
|
padding-left: 60px; |
||||
|
} |
||||
|
|
||||
|
dl dl pre { |
||||
|
margin-left: -90px; |
||||
|
padding-left: 90px; |
||||
|
} |
||||
|
|
||||
|
tt { |
||||
|
background-color: #E8EFF0; |
||||
|
color: #222; |
||||
|
/* padding: 1px 2px; */ |
||||
|
} |
||||
|
|
||||
|
tt.xref, a tt { |
||||
|
background-color: #E8EFF0; |
||||
|
border-bottom: 1px solid white; |
||||
|
} |
||||
|
|
||||
|
a.reference { |
||||
|
text-decoration: none; |
||||
|
border-bottom: 1px dotted #bb0000; |
||||
|
} |
||||
|
|
||||
|
a.reference:hover { |
||||
|
border-bottom: 1px solid #dd0000; |
||||
|
} |
||||
|
|
||||
|
a.footnote-reference { |
||||
|
text-decoration: none; |
||||
|
font-size: 0.7em; |
||||
|
vertical-align: top; |
||||
|
border-bottom: 1px dotted #bb0000; |
||||
|
} |
||||
|
|
||||
|
a.footnote-reference:hover { |
||||
|
border-bottom: 1px solid #dd0000; |
||||
|
} |
||||
|
|
||||
|
a:hover tt { |
||||
|
background: #EEE; |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
[theme] |
||||
|
inherit = basic |
||||
|
stylesheet = jinja.css |
@ -0,0 +1,808 @@ |
|||||
|
API |
||||
|
=== |
||||
|
|
||||
|
.. module:: jinja2 |
||||
|
:synopsis: public Jinja2 API |
||||
|
|
||||
|
This document describes the API to Jinja2 and not the template language. It |
||||
|
will be most useful as reference to those implementing the template interface |
||||
|
to the application and not those who are creating Jinja2 templates. |
||||
|
|
||||
|
Basics |
||||
|
------ |
||||
|
|
||||
|
Jinja2 uses a central object called the template :class:`Environment`. |
||||
|
Instances of this class are used to store the configuration and global objects, |
||||
|
and are used to load templates from the file system or other locations. |
||||
|
Even if you are creating templates from strings by using the constructor of |
||||
|
:class:`Template` class, an environment is created automatically for you, |
||||
|
albeit a shared one. |
||||
|
|
||||
|
Most applications will create one :class:`Environment` object on application |
||||
|
initialization and use that to load templates. In some cases it's however |
||||
|
useful to have multiple environments side by side, if different configurations |
||||
|
are in use. |
||||
|
|
||||
|
The simplest way to configure Jinja2 to load templates for your application |
||||
|
looks roughly like this:: |
||||
|
|
||||
|
from jinja2 import Environment, PackageLoader |
||||
|
env = Environment(loader=PackageLoader('yourapplication', 'templates')) |
||||
|
|
||||
|
This will create a template environment with the default settings and a |
||||
|
loader that looks up the templates in the `templates` folder inside the |
||||
|
`yourapplication` python package. Different loaders are available |
||||
|
and you can also write your own if you want to load templates from a |
||||
|
database or other resources. |
||||
|
|
||||
|
To load a template from this environment you just have to call the |
||||
|
:meth:`get_template` method which then returns the loaded :class:`Template`:: |
||||
|
|
||||
|
template = env.get_template('mytemplate.html') |
||||
|
|
||||
|
To render it with some variables, just call the :meth:`render` method:: |
||||
|
|
||||
|
print template.render(the='variables', go='here') |
||||
|
|
||||
|
Using a template loader rather than passing strings to :class:`Template` |
||||
|
or :meth:`Environment.from_string` has multiple advantages. Besides being |
||||
|
a lot easier to use it also enables template inheritance. |
||||
|
|
||||
|
|
||||
|
Unicode |
||||
|
------- |
||||
|
|
||||
|
Jinja2 is using Unicode internally which means that you have to pass Unicode |
||||
|
objects to the render function or bytestrings that only consist of ASCII |
||||
|
characters. Additionally newlines are normalized to one end of line |
||||
|
sequence which is per default UNIX style (``\n``). |
||||
|
|
||||
|
Python 2.x supports two ways of representing string objects. One is the |
||||
|
`str` type and the other is the `unicode` type, both of which extend a type |
||||
|
called `basestring`. Unfortunately the default is `str` which should not |
||||
|
be used to store text based information unless only ASCII characters are |
||||
|
used. With Python 2.6 it is possible to make `unicode` the default on a per |
||||
|
module level and with Python 3 it will be the default. |
||||
|
|
||||
|
To explicitly use a Unicode string you have to prefix the string literal |
||||
|
with a `u`: ``u'Hänsel und Gretel sagen Hallo'``. That way Python will |
||||
|
store the string as Unicode by decoding the string with the character |
||||
|
encoding from the current Python module. If no encoding is specified this |
||||
|
defaults to 'ASCII' which means that you can't use any non ASCII identifier. |
||||
|
|
||||
|
To set a better module encoding add the following comment to the first or |
||||
|
second line of the Python module using the Unicode literal:: |
||||
|
|
||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
We recommend utf-8 as Encoding for Python modules and templates as it's |
||||
|
possible to represent every Unicode character in utf-8 and because it's |
||||
|
backwards compatible to ASCII. For Jinja2 the default encoding of templates |
||||
|
is assumed to be utf-8. |
||||
|
|
||||
|
It is not possible to use Jinja2 to process non-Unicode data. The reason |
||||
|
for this is that Jinja2 uses Unicode already on the language level. For |
||||
|
example Jinja2 treats the non-breaking space as valid whitespace inside |
||||
|
expressions which requires knowledge of the encoding or operating on an |
||||
|
Unicode string. |
||||
|
|
||||
|
For more details about Unicode in Python have a look at the excellent |
||||
|
`Unicode documentation`_. |
||||
|
|
||||
|
Another important thing is how Jinja2 is handling string literals in |
||||
|
templates. A naive implementation would be using Unicode strings for |
||||
|
all string literals but it turned out in the past that this is problematic |
||||
|
as some libraries are typechecking against `str` explicitly. For example |
||||
|
`datetime.strftime` does not accept Unicode arguments. To not break it |
||||
|
completely Jinja2 is returning `str` for strings that fit into ASCII and |
||||
|
for everything else `unicode`: |
||||
|
|
||||
|
>>> m = Template(u"{% set a, b = 'foo', 'föö' %}").module |
||||
|
>>> m.a |
||||
|
'foo' |
||||
|
>>> m.b |
||||
|
u'f\xf6\xf6' |
||||
|
|
||||
|
|
||||
|
.. _Unicode documentation: http://docs.python.org/dev/howto/unicode.html |
||||
|
|
||||
|
High Level API |
||||
|
-------------- |
||||
|
|
||||
|
The high-level API is the API you will use in the application to load and |
||||
|
render Jinja2 templates. The :ref:`low-level-api` on the other side is only |
||||
|
useful if you want to dig deeper into Jinja2 or :ref:`develop extensions |
||||
|
<jinja-extensions>`. |
||||
|
|
||||
|
.. autoclass:: Environment([options]) |
||||
|
:members: from_string, get_template, select_template, |
||||
|
get_or_select_template, join_path, extend, compile_expression, |
||||
|
compile_templates, list_templates, add_extension |
||||
|
|
||||
|
.. attribute:: shared |
||||
|
|
||||
|
If a template was created by using the :class:`Template` constructor |
||||
|
an environment is created automatically. These environments are |
||||
|
created as shared environments which means that multiple templates |
||||
|
may have the same anonymous environment. For all shared environments |
||||
|
this attribute is `True`, else `False`. |
||||
|
|
||||
|
.. attribute:: sandboxed |
||||
|
|
||||
|
If the environment is sandboxed this attribute is `True`. For the |
||||
|
sandbox mode have a look at the documentation for the |
||||
|
:class:`~jinja2.sandbox.SandboxedEnvironment`. |
||||
|
|
||||
|
.. attribute:: filters |
||||
|
|
||||
|
A dict of filters for this environment. As long as no template was |
||||
|
loaded it's safe to add new filters or remove old. For custom filters |
||||
|
see :ref:`writing-filters`. For valid filter names have a look at |
||||
|
:ref:`identifier-naming`. |
||||
|
|
||||
|
.. attribute:: tests |
||||
|
|
||||
|
A dict of test functions for this environment. As long as no |
||||
|
template was loaded it's safe to modify this dict. For custom tests |
||||
|
see :ref:`writing-tests`. For valid test names have a look at |
||||
|
:ref:`identifier-naming`. |
||||
|
|
||||
|
.. attribute:: globals |
||||
|
|
||||
|
A dict of global variables. These variables are always available |
||||
|
in a template. As long as no template was loaded it's safe |
||||
|
to modify this dict. For more details see :ref:`global-namespace`. |
||||
|
For valid object names have a look at :ref:`identifier-naming`. |
||||
|
|
||||
|
.. attribute:: code_generator_class |
||||
|
|
||||
|
The class used for code generation. This should not be changed |
||||
|
in most cases, unless you need to modify the Python code a |
||||
|
template compiles to. |
||||
|
|
||||
|
.. attribute:: context_class |
||||
|
|
||||
|
The context used for templates. This should not be changed |
||||
|
in most cases, unless you need to modify internals of how |
||||
|
template variables are handled. For details, see |
||||
|
:class:`~jinja2.runtime.Context`. |
||||
|
|
||||
|
.. automethod:: overlay([options]) |
||||
|
|
||||
|
.. method:: undefined([hint, obj, name, exc]) |
||||
|
|
||||
|
Creates a new :class:`Undefined` object for `name`. This is useful |
||||
|
for filters or functions that may return undefined objects for |
||||
|
some operations. All parameters except of `hint` should be provided |
||||
|
as keyword parameters for better readability. The `hint` is used as |
||||
|
error message for the exception if provided, otherwise the error |
||||
|
message will be generated from `obj` and `name` automatically. The exception |
||||
|
provided as `exc` is raised if something with the generated undefined |
||||
|
object is done that the undefined object does not allow. The default |
||||
|
exception is :exc:`UndefinedError`. If a `hint` is provided the |
||||
|
`name` may be omitted. |
||||
|
|
||||
|
The most common way to create an undefined object is by providing |
||||
|
a name only:: |
||||
|
|
||||
|
return environment.undefined(name='some_name') |
||||
|
|
||||
|
This means that the name `some_name` is not defined. If the name |
||||
|
was from an attribute of an object it makes sense to tell the |
||||
|
undefined object the holder object to improve the error message:: |
||||
|
|
||||
|
if not hasattr(obj, 'attr'): |
||||
|
return environment.undefined(obj=obj, name='attr') |
||||
|
|
||||
|
For a more complex example you can provide a hint. For example |
||||
|
the :func:`first` filter creates an undefined object that way:: |
||||
|
|
||||
|
return environment.undefined('no first item, sequence was empty') |
||||
|
|
||||
|
If it the `name` or `obj` is known (for example because an attribute |
||||
|
was accessed) it should be passed to the undefined object, even if |
||||
|
a custom `hint` is provided. This gives undefined objects the |
||||
|
possibility to enhance the error message. |
||||
|
|
||||
|
.. autoclass:: Template |
||||
|
:members: module, make_module |
||||
|
|
||||
|
.. attribute:: globals |
||||
|
|
||||
|
The dict with the globals of that template. It's unsafe to modify |
||||
|
this dict as it may be shared with other templates or the environment |
||||
|
that loaded the template. |
||||
|
|
||||
|
.. attribute:: name |
||||
|
|
||||
|
The loading name of the template. If the template was loaded from a |
||||
|
string this is `None`. |
||||
|
|
||||
|
.. attribute:: filename |
||||
|
|
||||
|
The filename of the template on the file system if it was loaded from |
||||
|
there. Otherwise this is `None`. |
||||
|
|
||||
|
.. automethod:: render([context]) |
||||
|
|
||||
|
.. automethod:: generate([context]) |
||||
|
|
||||
|
.. automethod:: stream([context]) |
||||
|
|
||||
|
|
||||
|
.. autoclass:: jinja2.environment.TemplateStream() |
||||
|
:members: disable_buffering, enable_buffering, dump |
||||
|
|
||||
|
|
||||
|
Autoescaping |
||||
|
------------ |
||||
|
|
||||
|
.. versionadded:: 2.4 |
||||
|
|
||||
|
As of Jinja 2.4 the preferred way to do autoescaping is to enable the |
||||
|
:ref:`autoescape-extension` and to configure a sensible default for |
||||
|
autoescaping. This makes it possible to enable and disable autoescaping |
||||
|
on a per-template basis (HTML versus text for instance). |
||||
|
|
||||
|
Here a recommended setup that enables autoescaping for templates ending |
||||
|
in ``'.html'``, ``'.htm'`` and ``'.xml'`` and disabling it by default |
||||
|
for all other extensions:: |
||||
|
|
||||
|
def guess_autoescape(template_name): |
||||
|
if template_name is None or '.' not in template_name: |
||||
|
return False |
||||
|
ext = template_name.rsplit('.', 1)[1] |
||||
|
return ext in ('html', 'htm', 'xml') |
||||
|
|
||||
|
env = Environment(autoescape=guess_autoescape, |
||||
|
loader=PackageLoader('mypackage'), |
||||
|
extensions=['jinja2.ext.autoescape']) |
||||
|
|
||||
|
When implementing a guessing autoescape function, make sure you also |
||||
|
accept `None` as valid template name. This will be passed when generating |
||||
|
templates from strings. |
||||
|
|
||||
|
Inside the templates the behaviour can be temporarily changed by using |
||||
|
the `autoescape` block (see :ref:`autoescape-overrides`). |
||||
|
|
||||
|
|
||||
|
.. _identifier-naming: |
||||
|
|
||||
|
Notes on Identifiers |
||||
|
-------------------- |
||||
|
|
||||
|
Jinja2 uses the regular Python 2.x naming rules. Valid identifiers have to |
||||
|
match ``[a-zA-Z_][a-zA-Z0-9_]*``. As a matter of fact non ASCII characters |
||||
|
are currently not allowed. This limitation will probably go away as soon as |
||||
|
unicode identifiers are fully specified for Python 3. |
||||
|
|
||||
|
Filters and tests are looked up in separate namespaces and have slightly |
||||
|
modified identifier syntax. Filters and tests may contain dots to group |
||||
|
filters and tests by topic. For example it's perfectly valid to add a |
||||
|
function into the filter dict and call it `to.unicode`. The regular |
||||
|
expression for filter and test identifiers is |
||||
|
``[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*```. |
||||
|
|
||||
|
|
||||
|
Undefined Types |
||||
|
--------------- |
||||
|
|
||||
|
These classes can be used as undefined types. The :class:`Environment` |
||||
|
constructor takes an `undefined` parameter that can be one of those classes |
||||
|
or a custom subclass of :class:`Undefined`. Whenever the template engine is |
||||
|
unable to look up a name or access an attribute one of those objects is |
||||
|
created and returned. Some operations on undefined values are then allowed, |
||||
|
others fail. |
||||
|
|
||||
|
The closest to regular Python behavior is the `StrictUndefined` which |
||||
|
disallows all operations beside testing if it's an undefined object. |
||||
|
|
||||
|
.. autoclass:: jinja2.Undefined() |
||||
|
|
||||
|
.. attribute:: _undefined_hint |
||||
|
|
||||
|
Either `None` or an unicode string with the error message for |
||||
|
the undefined object. |
||||
|
|
||||
|
.. attribute:: _undefined_obj |
||||
|
|
||||
|
Either `None` or the owner object that caused the undefined object |
||||
|
to be created (for example because an attribute does not exist). |
||||
|
|
||||
|
.. attribute:: _undefined_name |
||||
|
|
||||
|
The name for the undefined variable / attribute or just `None` |
||||
|
if no such information exists. |
||||
|
|
||||
|
.. attribute:: _undefined_exception |
||||
|
|
||||
|
The exception that the undefined object wants to raise. This |
||||
|
is usually one of :exc:`UndefinedError` or :exc:`SecurityError`. |
||||
|
|
||||
|
.. method:: _fail_with_undefined_error(\*args, \**kwargs) |
||||
|
|
||||
|
When called with any arguments this method raises |
||||
|
:attr:`_undefined_exception` with an error message generated |
||||
|
from the undefined hints stored on the undefined object. |
||||
|
|
||||
|
.. autoclass:: jinja2.DebugUndefined() |
||||
|
|
||||
|
.. autoclass:: jinja2.StrictUndefined() |
||||
|
|
||||
|
There is also a factory function that can decorate undefined objects to |
||||
|
implement logging on failures: |
||||
|
|
||||
|
.. autofunction:: jinja2.make_logging_undefined |
||||
|
|
||||
|
Undefined objects are created by calling :attr:`undefined`. |
||||
|
|
||||
|
.. admonition:: Implementation |
||||
|
|
||||
|
:class:`Undefined` objects are implemented by overriding the special |
||||
|
`__underscore__` methods. For example the default :class:`Undefined` |
||||
|
class implements `__unicode__` in a way that it returns an empty |
||||
|
string, however `__int__` and others still fail with an exception. To |
||||
|
allow conversion to int by returning ``0`` you can implement your own:: |
||||
|
|
||||
|
class NullUndefined(Undefined): |
||||
|
def __int__(self): |
||||
|
return 0 |
||||
|
def __float__(self): |
||||
|
return 0.0 |
||||
|
|
||||
|
To disallow a method, just override it and raise |
||||
|
:attr:`~Undefined._undefined_exception`. Because this is a very common |
||||
|
idom in undefined objects there is the helper method |
||||
|
:meth:`~Undefined._fail_with_undefined_error` that does the error raising |
||||
|
automatically. Here a class that works like the regular :class:`Undefined` |
||||
|
but chokes on iteration:: |
||||
|
|
||||
|
class NonIterableUndefined(Undefined): |
||||
|
__iter__ = Undefined._fail_with_undefined_error |
||||
|
|
||||
|
|
||||
|
The Context |
||||
|
----------- |
||||
|
|
||||
|
.. autoclass:: jinja2.runtime.Context() |
||||
|
:members: resolve, get_exported, get_all |
||||
|
|
||||
|
.. attribute:: parent |
||||
|
|
||||
|
A dict of read only, global variables the template looks up. These |
||||
|
can either come from another :class:`Context`, from the |
||||
|
:attr:`Environment.globals` or :attr:`Template.globals` or points |
||||
|
to a dict created by combining the globals with the variables |
||||
|
passed to the render function. It must not be altered. |
||||
|
|
||||
|
.. attribute:: vars |
||||
|
|
||||
|
The template local variables. This list contains environment and |
||||
|
context functions from the :attr:`parent` scope as well as local |
||||
|
modifications and exported variables from the template. The template |
||||
|
will modify this dict during template evaluation but filters and |
||||
|
context functions are not allowed to modify it. |
||||
|
|
||||
|
.. attribute:: environment |
||||
|
|
||||
|
The environment that loaded the template. |
||||
|
|
||||
|
.. attribute:: exported_vars |
||||
|
|
||||
|
This set contains all the names the template exports. The values for |
||||
|
the names are in the :attr:`vars` dict. In order to get a copy of the |
||||
|
exported variables as dict, :meth:`get_exported` can be used. |
||||
|
|
||||
|
.. attribute:: name |
||||
|
|
||||
|
The load name of the template owning this context. |
||||
|
|
||||
|
.. attribute:: blocks |
||||
|
|
||||
|
A dict with the current mapping of blocks in the template. The keys |
||||
|
in this dict are the names of the blocks, and the values a list of |
||||
|
blocks registered. The last item in each list is the current active |
||||
|
block (latest in the inheritance chain). |
||||
|
|
||||
|
.. attribute:: eval_ctx |
||||
|
|
||||
|
The current :ref:`eval-context`. |
||||
|
|
||||
|
.. automethod:: jinja2.runtime.Context.call(callable, \*args, \**kwargs) |
||||
|
|
||||
|
|
||||
|
.. admonition:: Implementation |
||||
|
|
||||
|
Context is immutable for the same reason Python's frame locals are |
||||
|
immutable inside functions. Both Jinja2 and Python are not using the |
||||
|
context / frame locals as data storage for variables but only as primary |
||||
|
data source. |
||||
|
|
||||
|
When a template accesses a variable the template does not define, Jinja2 |
||||
|
looks up the variable in the context, after that the variable is treated |
||||
|
as if it was defined in the template. |
||||
|
|
||||
|
|
||||
|
.. _loaders: |
||||
|
|
||||
|
Loaders |
||||
|
------- |
||||
|
|
||||
|
Loaders are responsible for loading templates from a resource such as the |
||||
|
file system. The environment will keep the compiled modules in memory like |
||||
|
Python's `sys.modules`. Unlike `sys.modules` however this cache is limited in |
||||
|
size by default and templates are automatically reloaded. |
||||
|
All loaders are subclasses of :class:`BaseLoader`. If you want to create your |
||||
|
own loader, subclass :class:`BaseLoader` and override `get_source`. |
||||
|
|
||||
|
.. autoclass:: jinja2.BaseLoader |
||||
|
:members: get_source, load |
||||
|
|
||||
|
Here a list of the builtin loaders Jinja2 provides: |
||||
|
|
||||
|
.. autoclass:: jinja2.FileSystemLoader |
||||
|
|
||||
|
.. autoclass:: jinja2.PackageLoader |
||||
|
|
||||
|
.. autoclass:: jinja2.DictLoader |
||||
|
|
||||
|
.. autoclass:: jinja2.FunctionLoader |
||||
|
|
||||
|
.. autoclass:: jinja2.PrefixLoader |
||||
|
|
||||
|
.. autoclass:: jinja2.ChoiceLoader |
||||
|
|
||||
|
.. autoclass:: jinja2.ModuleLoader |
||||
|
|
||||
|
|
||||
|
.. _bytecode-cache: |
||||
|
|
||||
|
Bytecode Cache |
||||
|
-------------- |
||||
|
|
||||
|
Jinja 2.1 and higher support external bytecode caching. Bytecode caches make |
||||
|
it possible to store the generated bytecode on the file system or a different |
||||
|
location to avoid parsing the templates on first use. |
||||
|
|
||||
|
This is especially useful if you have a web application that is initialized on |
||||
|
the first request and Jinja compiles many templates at once which slows down |
||||
|
the application. |
||||
|
|
||||
|
To use a bytecode cache, instantiate it and pass it to the :class:`Environment`. |
||||
|
|
||||
|
.. autoclass:: jinja2.BytecodeCache |
||||
|
:members: load_bytecode, dump_bytecode, clear |
||||
|
|
||||
|
.. autoclass:: jinja2.bccache.Bucket |
||||
|
:members: write_bytecode, load_bytecode, bytecode_from_string, |
||||
|
bytecode_to_string, reset |
||||
|
|
||||
|
.. attribute:: environment |
||||
|
|
||||
|
The :class:`Environment` that created the bucket. |
||||
|
|
||||
|
.. attribute:: key |
||||
|
|
||||
|
The unique cache key for this bucket |
||||
|
|
||||
|
.. attribute:: code |
||||
|
|
||||
|
The bytecode if it's loaded, otherwise `None`. |
||||
|
|
||||
|
|
||||
|
Builtin bytecode caches: |
||||
|
|
||||
|
.. autoclass:: jinja2.FileSystemBytecodeCache |
||||
|
|
||||
|
.. autoclass:: jinja2.MemcachedBytecodeCache |
||||
|
|
||||
|
|
||||
|
Utilities |
||||
|
--------- |
||||
|
|
||||
|
These helper functions and classes are useful if you add custom filters or |
||||
|
functions to a Jinja2 environment. |
||||
|
|
||||
|
.. autofunction:: jinja2.environmentfilter |
||||
|
|
||||
|
.. autofunction:: jinja2.contextfilter |
||||
|
|
||||
|
.. autofunction:: jinja2.evalcontextfilter |
||||
|
|
||||
|
.. autofunction:: jinja2.environmentfunction |
||||
|
|
||||
|
.. autofunction:: jinja2.contextfunction |
||||
|
|
||||
|
.. autofunction:: jinja2.evalcontextfunction |
||||
|
|
||||
|
.. function:: escape(s) |
||||
|
|
||||
|
Convert the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in string `s` |
||||
|
to HTML-safe sequences. Use this if you need to display text that might |
||||
|
contain such characters in HTML. This function will not escaped objects |
||||
|
that do have an HTML representation such as already escaped data. |
||||
|
|
||||
|
The return value is a :class:`Markup` string. |
||||
|
|
||||
|
.. autofunction:: jinja2.clear_caches |
||||
|
|
||||
|
.. autofunction:: jinja2.is_undefined |
||||
|
|
||||
|
.. autoclass:: jinja2.Markup([string]) |
||||
|
:members: escape, unescape, striptags |
||||
|
|
||||
|
.. admonition:: Note |
||||
|
|
||||
|
The Jinja2 :class:`Markup` class is compatible with at least Pylons and |
||||
|
Genshi. It's expected that more template engines and framework will pick |
||||
|
up the `__html__` concept soon. |
||||
|
|
||||
|
|
||||
|
Exceptions |
||||
|
---------- |
||||
|
|
||||
|
.. autoexception:: jinja2.TemplateError |
||||
|
|
||||
|
.. autoexception:: jinja2.UndefinedError |
||||
|
|
||||
|
.. autoexception:: jinja2.TemplateNotFound |
||||
|
|
||||
|
.. autoexception:: jinja2.TemplatesNotFound |
||||
|
|
||||
|
.. autoexception:: jinja2.TemplateSyntaxError |
||||
|
|
||||
|
.. attribute:: message |
||||
|
|
||||
|
The error message as utf-8 bytestring. |
||||
|
|
||||
|
.. attribute:: lineno |
||||
|
|
||||
|
The line number where the error occurred |
||||
|
|
||||
|
.. attribute:: name |
||||
|
|
||||
|
The load name for the template as unicode string. |
||||
|
|
||||
|
.. attribute:: filename |
||||
|
|
||||
|
The filename that loaded the template as bytestring in the encoding |
||||
|
of the file system (most likely utf-8 or mbcs on Windows systems). |
||||
|
|
||||
|
The reason why the filename and error message are bytestrings and not |
||||
|
unicode strings is that Python 2.x is not using unicode for exceptions |
||||
|
and tracebacks as well as the compiler. This will change with Python 3. |
||||
|
|
||||
|
.. autoexception:: jinja2.TemplateAssertionError |
||||
|
|
||||
|
|
||||
|
.. _writing-filters: |
||||
|
|
||||
|
Custom Filters |
||||
|
-------------- |
||||
|
|
||||
|
Custom filters are just regular Python functions that take the left side of |
||||
|
the filter as first argument and the the arguments passed to the filter as |
||||
|
extra arguments or keyword arguments. |
||||
|
|
||||
|
For example in the filter ``{{ 42|myfilter(23) }}`` the function would be |
||||
|
called with ``myfilter(42, 23)``. Here for example a simple filter that can |
||||
|
be applied to datetime objects to format them:: |
||||
|
|
||||
|
def datetimeformat(value, format='%H:%M / %d-%m-%Y'): |
||||
|
return value.strftime(format) |
||||
|
|
||||
|
You can register it on the template environment by updating the |
||||
|
:attr:`~Environment.filters` dict on the environment:: |
||||
|
|
||||
|
environment.filters['datetimeformat'] = datetimeformat |
||||
|
|
||||
|
Inside the template it can then be used as follows: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
written on: {{ article.pub_date|datetimeformat }} |
||||
|
publication date: {{ article.pub_date|datetimeformat('%d-%m-%Y') }} |
||||
|
|
||||
|
Filters can also be passed the current template context or environment. This |
||||
|
is useful if a filter wants to return an undefined value or check the current |
||||
|
:attr:`~Environment.autoescape` setting. For this purpose three decorators |
||||
|
exist: :func:`environmentfilter`, :func:`contextfilter` and |
||||
|
:func:`evalcontextfilter`. |
||||
|
|
||||
|
Here a small example filter that breaks a text into HTML line breaks and |
||||
|
paragraphs and marks the return value as safe HTML string if autoescaping is |
||||
|
enabled:: |
||||
|
|
||||
|
import re |
||||
|
from jinja2 import evalcontextfilter, Markup, escape |
||||
|
|
||||
|
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}') |
||||
|
|
||||
|
@evalcontextfilter |
||||
|
def nl2br(eval_ctx, value): |
||||
|
result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', Markup('<br>\n')) |
||||
|
for p in _paragraph_re.split(escape(value))) |
||||
|
if eval_ctx.autoescape: |
||||
|
result = Markup(result) |
||||
|
return result |
||||
|
|
||||
|
Context filters work the same just that the first argument is the current |
||||
|
active :class:`Context` rather then the environment. |
||||
|
|
||||
|
|
||||
|
.. _eval-context: |
||||
|
|
||||
|
Evaluation Context |
||||
|
------------------ |
||||
|
|
||||
|
The evaluation context (short eval context or eval ctx) is a new object |
||||
|
introduced in Jinja 2.4 that makes it possible to activate and deactivate |
||||
|
compiled features at runtime. |
||||
|
|
||||
|
Currently it is only used to enable and disable the automatic escaping but |
||||
|
can be used for extensions as well. |
||||
|
|
||||
|
In previous Jinja versions filters and functions were marked as |
||||
|
environment callables in order to check for the autoescape status from the |
||||
|
environment. In new versions it's encouraged to check the setting from the |
||||
|
evaluation context instead. |
||||
|
|
||||
|
Previous versions:: |
||||
|
|
||||
|
@environmentfilter |
||||
|
def filter(env, value): |
||||
|
result = do_something(value) |
||||
|
if env.autoescape: |
||||
|
result = Markup(result) |
||||
|
return result |
||||
|
|
||||
|
In new versions you can either use a :func:`contextfilter` and access the |
||||
|
evaluation context from the actual context, or use a |
||||
|
:func:`evalcontextfilter` which directly passes the evaluation context to |
||||
|
the function:: |
||||
|
|
||||
|
@contextfilter |
||||
|
def filter(context, value): |
||||
|
result = do_something(value) |
||||
|
if context.eval_ctx.autoescape: |
||||
|
result = Markup(result) |
||||
|
return result |
||||
|
|
||||
|
@evalcontextfilter |
||||
|
def filter(eval_ctx, value): |
||||
|
result = do_something(value) |
||||
|
if eval_ctx.autoescape: |
||||
|
result = Markup(result) |
||||
|
return result |
||||
|
|
||||
|
The evaluation context must not be modified at runtime. Modifications |
||||
|
must only happen with a :class:`nodes.EvalContextModifier` and |
||||
|
:class:`nodes.ScopedEvalContextModifier` from an extension, not on the |
||||
|
eval context object itself. |
||||
|
|
||||
|
.. autoclass:: jinja2.nodes.EvalContext |
||||
|
|
||||
|
.. attribute:: autoescape |
||||
|
|
||||
|
`True` or `False` depending on if autoescaping is active or not. |
||||
|
|
||||
|
.. attribute:: volatile |
||||
|
|
||||
|
`True` if the compiler cannot evaluate some expressions at compile |
||||
|
time. At runtime this should always be `False`. |
||||
|
|
||||
|
|
||||
|
.. _writing-tests: |
||||
|
|
||||
|
Custom Tests |
||||
|
------------ |
||||
|
|
||||
|
Tests work like filters just that there is no way for a test to get access |
||||
|
to the environment or context and that they can't be chained. The return |
||||
|
value of a test should be `True` or `False`. The purpose of a test is to |
||||
|
give the template designers the possibility to perform type and conformability |
||||
|
checks. |
||||
|
|
||||
|
Here a simple test that checks if a variable is a prime number:: |
||||
|
|
||||
|
import math |
||||
|
|
||||
|
def is_prime(n): |
||||
|
if n == 2: |
||||
|
return True |
||||
|
for i in xrange(2, int(math.ceil(math.sqrt(n))) + 1): |
||||
|
if n % i == 0: |
||||
|
return False |
||||
|
return True |
||||
|
|
||||
|
|
||||
|
You can register it on the template environment by updating the |
||||
|
:attr:`~Environment.tests` dict on the environment:: |
||||
|
|
||||
|
environment.tests['prime'] = is_prime |
||||
|
|
||||
|
A template designer can then use the test like this: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{% if 42 is prime %} |
||||
|
42 is a prime number |
||||
|
{% else %} |
||||
|
42 is not a prime number |
||||
|
{% endif %} |
||||
|
|
||||
|
|
||||
|
.. _global-namespace: |
||||
|
|
||||
|
The Global Namespace |
||||
|
-------------------- |
||||
|
|
||||
|
Variables stored in the :attr:`Environment.globals` dict are special as they |
||||
|
are available for imported templates too, even if they are imported without |
||||
|
context. This is the place where you can put variables and functions |
||||
|
that should be available all the time. Additionally :attr:`Template.globals` |
||||
|
exist that are variables available to a specific template that are available |
||||
|
to all :meth:`~Template.render` calls. |
||||
|
|
||||
|
|
||||
|
.. _low-level-api: |
||||
|
|
||||
|
Low Level API |
||||
|
------------- |
||||
|
|
||||
|
The low level API exposes functionality that can be useful to understand some |
||||
|
implementation details, debugging purposes or advanced :ref:`extension |
||||
|
<jinja-extensions>` techniques. Unless you know exactly what you are doing we |
||||
|
don't recommend using any of those. |
||||
|
|
||||
|
.. automethod:: Environment.lex |
||||
|
|
||||
|
.. automethod:: Environment.parse |
||||
|
|
||||
|
.. automethod:: Environment.preprocess |
||||
|
|
||||
|
.. automethod:: Template.new_context |
||||
|
|
||||
|
.. method:: Template.root_render_func(context) |
||||
|
|
||||
|
This is the low level render function. It's passed a :class:`Context` |
||||
|
that has to be created by :meth:`new_context` of the same template or |
||||
|
a compatible template. This render function is generated by the |
||||
|
compiler from the template code and returns a generator that yields |
||||
|
unicode strings. |
||||
|
|
||||
|
If an exception in the template code happens the template engine will |
||||
|
not rewrite the exception but pass through the original one. As a |
||||
|
matter of fact this function should only be called from within a |
||||
|
:meth:`render` / :meth:`generate` / :meth:`stream` call. |
||||
|
|
||||
|
.. attribute:: Template.blocks |
||||
|
|
||||
|
A dict of block render functions. Each of these functions works exactly |
||||
|
like the :meth:`root_render_func` with the same limitations. |
||||
|
|
||||
|
.. attribute:: Template.is_up_to_date |
||||
|
|
||||
|
This attribute is `False` if there is a newer version of the template |
||||
|
available, otherwise `True`. |
||||
|
|
||||
|
.. admonition:: Note |
||||
|
|
||||
|
The low-level API is fragile. Future Jinja2 versions will try not to |
||||
|
change it in a backwards incompatible way but modifications in the Jinja2 |
||||
|
core may shine through. For example if Jinja2 introduces a new AST node |
||||
|
in later versions that may be returned by :meth:`~Environment.parse`. |
||||
|
|
||||
|
The Meta API |
||||
|
------------ |
||||
|
|
||||
|
.. versionadded:: 2.2 |
||||
|
|
||||
|
The meta API returns some information about abstract syntax trees that |
||||
|
could help applications to implement more advanced template concepts. All |
||||
|
the functions of the meta API operate on an abstract syntax tree as |
||||
|
returned by the :meth:`Environment.parse` method. |
||||
|
|
||||
|
.. autofunction:: jinja2.meta.find_undeclared_variables |
||||
|
|
||||
|
.. autofunction:: jinja2.meta.find_referenced_templates |
@ -0,0 +1,56 @@ |
|||||
|
from jinja2 import nodes |
||||
|
from jinja2.ext import Extension |
||||
|
|
||||
|
|
||||
|
class FragmentCacheExtension(Extension): |
||||
|
# a set of names that trigger the extension. |
||||
|
tags = set(['cache']) |
||||
|
|
||||
|
def __init__(self, environment): |
||||
|
super(FragmentCacheExtension, self).__init__(environment) |
||||
|
|
||||
|
# add the defaults to the environment |
||||
|
environment.extend( |
||||
|
fragment_cache_prefix='', |
||||
|
fragment_cache=None |
||||
|
) |
||||
|
|
||||
|
def parse(self, parser): |
||||
|
# the first token is the token that started the tag. In our case |
||||
|
# we only listen to ``'cache'`` so this will be a name token with |
||||
|
# `cache` as value. We get the line number so that we can give |
||||
|
# that line number to the nodes we create by hand. |
||||
|
lineno = next(parser.stream).lineno |
||||
|
|
||||
|
# now we parse a single expression that is used as cache key. |
||||
|
args = [parser.parse_expression()] |
||||
|
|
||||
|
# if there is a comma, the user provided a timeout. If not use |
||||
|
# None as second parameter. |
||||
|
if parser.stream.skip_if('comma'): |
||||
|
args.append(parser.parse_expression()) |
||||
|
else: |
||||
|
args.append(nodes.Const(None)) |
||||
|
|
||||
|
# now we parse the body of the cache block up to `endcache` and |
||||
|
# drop the needle (which would always be `endcache` in that case) |
||||
|
body = parser.parse_statements(['name:endcache'], drop_needle=True) |
||||
|
|
||||
|
# now return a `CallBlock` node that calls our _cache_support |
||||
|
# helper method on this extension. |
||||
|
return nodes.CallBlock(self.call_method('_cache_support', args), |
||||
|
[], [], body).set_lineno(lineno) |
||||
|
|
||||
|
def _cache_support(self, name, timeout, caller): |
||||
|
"""Helper callback.""" |
||||
|
key = self.environment.fragment_cache_prefix + name |
||||
|
|
||||
|
# try to load the block from the cache |
||||
|
# if there is no fragment in the cache, render it and store |
||||
|
# it in the cache. |
||||
|
rv = self.environment.fragment_cache.get(key) |
||||
|
if rv is not None: |
||||
|
return rv |
||||
|
rv = caller() |
||||
|
self.environment.fragment_cache.add(key, rv, timeout) |
||||
|
return rv |
@ -0,0 +1,3 @@ |
|||||
|
.. module:: jinja2 |
||||
|
|
||||
|
.. include:: ../CHANGES |
@ -0,0 +1,160 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# |
||||
|
# Jinja2 documentation build configuration file, created by |
||||
|
# sphinx-quickstart on Sun Apr 27 21:42:41 2008. |
||||
|
# |
||||
|
# This file is execfile()d with the current directory set to its containing dir. |
||||
|
# |
||||
|
# The contents of this file are pickled, so don't put values in the namespace |
||||
|
# that aren't pickleable (module imports are okay, they're removed automatically). |
||||
|
# |
||||
|
# All configuration values have a default value; values that are commented out |
||||
|
# serve to show the default value. |
||||
|
|
||||
|
import sys, os |
||||
|
|
||||
|
# If your extensions are in another directory, add it here. If the directory |
||||
|
# is relative to the documentation root, use os.path.abspath to make it |
||||
|
# absolute, like shown here. |
||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__))) |
||||
|
|
||||
|
# General configuration |
||||
|
# --------------------- |
||||
|
|
||||
|
# Add any Sphinx extension module names here, as strings. They can be extensions |
||||
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. |
||||
|
extensions = ['sphinx.ext.autodoc', 'jinjaext'] |
||||
|
|
||||
|
# Add any paths that contain templates here, relative to this directory. |
||||
|
templates_path = ['_templates'] |
||||
|
|
||||
|
# The suffix of source filenames. |
||||
|
source_suffix = '.rst' |
||||
|
|
||||
|
# The master toctree document. |
||||
|
master_doc = 'index' |
||||
|
|
||||
|
# General substitutions. |
||||
|
project = 'Jinja2' |
||||
|
copyright = '2008, Armin Ronacher' |
||||
|
|
||||
|
# The default replacements for |version| and |release|, also used in various |
||||
|
# other places throughout the built documents. |
||||
|
# |
||||
|
# The short X.Y version. |
||||
|
import pkg_resources |
||||
|
try: |
||||
|
release = pkg_resources.get_distribution('Jinja2').version |
||||
|
except ImportError: |
||||
|
print 'To build the documentation, The distribution information of Jinja2' |
||||
|
print 'Has to be available. Either install the package into your' |
||||
|
print 'development environment or run "setup.py develop" to setup the' |
||||
|
print 'metadata. A virtualenv is recommended!' |
||||
|
sys.exit(1) |
||||
|
if 'dev' in release: |
||||
|
release = release.split('dev')[0] + 'dev' |
||||
|
version = '.'.join(release.split('.')[:2]) |
||||
|
|
||||
|
# There are two options for replacing |today|: either, you set today to some |
||||
|
# non-false value, then it is used: |
||||
|
#today = '' |
||||
|
# Else, today_fmt is used as the format for a strftime call. |
||||
|
today_fmt = '%B %d, %Y' |
||||
|
|
||||
|
# List of documents that shouldn't be included in the build. |
||||
|
#unused_docs = [] |
||||
|
|
||||
|
# If true, '()' will be appended to :func: etc. cross-reference text. |
||||
|
#add_function_parentheses = True |
||||
|
|
||||
|
# If true, the current module name will be prepended to all description |
||||
|
# unit titles (such as .. function::). |
||||
|
#add_module_names = True |
||||
|
|
||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the |
||||
|
# output. They are ignored by default. |
||||
|
#show_authors = False |
||||
|
|
||||
|
# The name of the Pygments (syntax highlighting) style to use. |
||||
|
pygments_style = 'jinjaext.JinjaStyle' |
||||
|
|
||||
|
|
||||
|
# Options for HTML output |
||||
|
# ----------------------- |
||||
|
|
||||
|
html_theme = 'jinja' |
||||
|
html_theme_path = ['_themes'] |
||||
|
|
||||
|
# The name for this set of Sphinx documents. If None, it defaults to |
||||
|
# "<project> v<release> documentation". |
||||
|
#html_title = None |
||||
|
|
||||
|
# Add any paths that contain custom static files (such as style sheets) here, |
||||
|
# relative to this directory. They are copied after the builtin static files, |
||||
|
# so a file named "default.css" will overwrite the builtin "default.css". |
||||
|
html_static_path = ['_static'] |
||||
|
|
||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, |
||||
|
# using the given strftime format. |
||||
|
html_last_updated_fmt = '%b %d, %Y' |
||||
|
|
||||
|
# If true, SmartyPants will be used to convert quotes and dashes to |
||||
|
# typographically correct entities. |
||||
|
#html_use_smartypants = True |
||||
|
|
||||
|
# no modindex |
||||
|
html_use_modindex = False |
||||
|
|
||||
|
# If true, the reST sources are included in the HTML build as _sources/<name>. |
||||
|
#html_copy_source = True |
||||
|
|
||||
|
# If true, an OpenSearch description file will be output, and all pages will |
||||
|
# contain a <link> tag referring to it. |
||||
|
#html_use_opensearch = False |
||||
|
|
||||
|
# Output file base name for HTML help builder. |
||||
|
htmlhelp_basename = 'Jinja2doc' |
||||
|
|
||||
|
|
||||
|
# Options for LaTeX output |
||||
|
# ------------------------ |
||||
|
|
||||
|
# The paper size ('letter' or 'a4'). |
||||
|
latex_paper_size = 'a4' |
||||
|
|
||||
|
# The font size ('10pt', '11pt' or '12pt'). |
||||
|
#latex_font_size = '10pt' |
||||
|
|
||||
|
# Grouping the document tree into LaTeX files. List of tuples |
||||
|
# (source start file, target name, title, author, document class [howto/manual]). |
||||
|
latex_documents = [ |
||||
|
('latexindex', 'Jinja2.tex', 'Jinja2 Documentation', 'Armin Ronacher', |
||||
|
'manual'), |
||||
|
] |
||||
|
|
||||
|
# Additional stuff for LaTeX |
||||
|
latex_elements = { |
||||
|
'fontpkg': r'\usepackage{mathpazo}', |
||||
|
'papersize': 'a4paper', |
||||
|
'pointsize': '12pt', |
||||
|
'preamble': r''' |
||||
|
\usepackage{jinjastyle} |
||||
|
|
||||
|
% i hate you latex |
||||
|
\DeclareUnicodeCharacter{14D}{o} |
||||
|
''' |
||||
|
} |
||||
|
|
||||
|
latex_use_parts = True |
||||
|
|
||||
|
latex_additional_files = ['jinjastyle.sty', 'logo.pdf'] |
||||
|
|
||||
|
# If false, no module index is generated. |
||||
|
latex_use_modindex = False |
||||
|
|
||||
|
html_sidebars = { |
||||
|
'index': ['sidebarlogo.html', 'sidebarintro.html', 'sourcelink.html', |
||||
|
'searchbox.html'], |
||||
|
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', |
||||
|
'sourcelink.html', 'searchbox.html'] |
||||
|
} |
@ -0,0 +1,23 @@ |
|||||
|
Jinja2 Documentation |
||||
|
-------------------- |
||||
|
|
||||
|
.. toctree:: |
||||
|
:maxdepth: 2 |
||||
|
|
||||
|
intro |
||||
|
api |
||||
|
sandbox |
||||
|
templates |
||||
|
extensions |
||||
|
integration |
||||
|
switching |
||||
|
tricks |
||||
|
|
||||
|
Additional Information |
||||
|
---------------------- |
||||
|
|
||||
|
.. toctree:: |
||||
|
:maxdepth: 2 |
||||
|
|
||||
|
faq |
||||
|
changelog |
@ -0,0 +1,346 @@ |
|||||
|
.. _jinja-extensions: |
||||
|
|
||||
|
Extensions |
||||
|
========== |
||||
|
|
||||
|
Jinja2 supports extensions that can add extra filters, tests, globals or even |
||||
|
extend the parser. The main motivation of extensions is to move often used |
||||
|
code into a reusable class like adding support for internationalization. |
||||
|
|
||||
|
|
||||
|
Adding Extensions |
||||
|
----------------- |
||||
|
|
||||
|
Extensions are added to the Jinja2 environment at creation time. Once the |
||||
|
environment is created additional extensions cannot be added. To add an |
||||
|
extension pass a list of extension classes or import paths to the |
||||
|
`extensions` parameter of the :class:`Environment` constructor. The following |
||||
|
example creates a Jinja2 environment with the i18n extension loaded:: |
||||
|
|
||||
|
jinja_env = Environment(extensions=['jinja2.ext.i18n']) |
||||
|
|
||||
|
|
||||
|
.. _i18n-extension: |
||||
|
|
||||
|
i18n Extension |
||||
|
-------------- |
||||
|
|
||||
|
**Import name:** `jinja2.ext.i18n` |
||||
|
|
||||
|
The i18n extension can be used in combination with `gettext`_ or `babel`_. If |
||||
|
the i18n extension is enabled Jinja2 provides a `trans` statement that marks |
||||
|
the wrapped string as translatable and calls `gettext`. |
||||
|
|
||||
|
After enabling, dummy `_` function that forwards calls to `gettext` is added |
||||
|
to the environment globals. An internationalized application then has to |
||||
|
provide a `gettext` function and optionally an `ngettext` function into the |
||||
|
namespace, either globally or for each rendering. |
||||
|
|
||||
|
Environment Methods |
||||
|
~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
After enabling the extension, the environment provides the following |
||||
|
additional methods: |
||||
|
|
||||
|
.. method:: jinja2.Environment.install_gettext_translations(translations, newstyle=False) |
||||
|
|
||||
|
Installs a translation globally for that environment. The translations |
||||
|
object provided must implement at least `ugettext` and `ungettext`. |
||||
|
The `gettext.NullTranslations` and `gettext.GNUTranslations` classes |
||||
|
as well as `Babel`_\s `Translations` class are supported. |
||||
|
|
||||
|
.. versionchanged:: 2.5 newstyle gettext added |
||||
|
|
||||
|
.. method:: jinja2.Environment.install_null_translations(newstyle=False) |
||||
|
|
||||
|
Install dummy gettext functions. This is useful if you want to prepare |
||||
|
the application for internationalization but don't want to implement the |
||||
|
full internationalization system yet. |
||||
|
|
||||
|
.. versionchanged:: 2.5 newstyle gettext added |
||||
|
|
||||
|
.. method:: jinja2.Environment.install_gettext_callables(gettext, ngettext, newstyle=False) |
||||
|
|
||||
|
Installs the given `gettext` and `ngettext` callables into the |
||||
|
environment as globals. They are supposed to behave exactly like the |
||||
|
standard library's :func:`gettext.ugettext` and |
||||
|
:func:`gettext.ungettext` functions. |
||||
|
|
||||
|
If `newstyle` is activated, the callables are wrapped to work like |
||||
|
newstyle callables. See :ref:`newstyle-gettext` for more information. |
||||
|
|
||||
|
.. versionadded:: 2.5 |
||||
|
|
||||
|
.. method:: jinja2.Environment.uninstall_gettext_translations() |
||||
|
|
||||
|
Uninstall the translations again. |
||||
|
|
||||
|
.. method:: jinja2.Environment.extract_translations(source) |
||||
|
|
||||
|
Extract localizable strings from the given template node or source. |
||||
|
|
||||
|
For every string found this function yields a ``(lineno, function, |
||||
|
message)`` tuple, where: |
||||
|
|
||||
|
* `lineno` is the number of the line on which the string was found, |
||||
|
* `function` is the name of the `gettext` function used (if the |
||||
|
string was extracted from embedded Python code), and |
||||
|
* `message` is the string itself (a `unicode` object, or a tuple |
||||
|
of `unicode` objects for functions with multiple string arguments). |
||||
|
|
||||
|
If `Babel`_ is installed, :ref:`the babel integration <babel-integration>` |
||||
|
can be used to extract strings for babel. |
||||
|
|
||||
|
For a web application that is available in multiple languages but gives all |
||||
|
the users the same language (for example a multilingual forum software |
||||
|
installed for a French community) may load the translations once and add the |
||||
|
translation methods to the environment at environment generation time:: |
||||
|
|
||||
|
translations = get_gettext_translations() |
||||
|
env = Environment(extensions=['jinja2.ext.i18n']) |
||||
|
env.install_gettext_translations(translations) |
||||
|
|
||||
|
The `get_gettext_translations` function would return the translator for the |
||||
|
current configuration. (For example by using `gettext.find`) |
||||
|
|
||||
|
The usage of the `i18n` extension for template designers is covered as part |
||||
|
:ref:`of the template documentation <i18n-in-templates>`. |
||||
|
|
||||
|
.. _gettext: http://docs.python.org/dev/library/gettext |
||||
|
.. _Babel: http://babel.edgewall.org/ |
||||
|
|
||||
|
.. _newstyle-gettext: |
||||
|
|
||||
|
Newstyle Gettext |
||||
|
~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
.. versionadded:: 2.5 |
||||
|
|
||||
|
Starting with version 2.5 you can use newstyle gettext calls. These are |
||||
|
inspired by trac's internal gettext functions and are fully supported by |
||||
|
the babel extraction tool. They might not work as expected by other |
||||
|
extraction tools in case you are not using Babel's. |
||||
|
|
||||
|
What's the big difference between standard and newstyle gettext calls? In |
||||
|
general they are less to type and less error prone. Also if they are used |
||||
|
in an autoescaping environment they better support automatic escaping. |
||||
|
Here are some common differences between old and new calls: |
||||
|
|
||||
|
standard gettext: |
||||
|
|
||||
|
.. sourcecode:: html+jinja |
||||
|
|
||||
|
{{ gettext('Hello World!') }} |
||||
|
{{ gettext('Hello %(name)s!')|format(name='World') }} |
||||
|
{{ ngettext('%(num)d apple', '%(num)d apples', apples|count)|format( |
||||
|
num=apples|count |
||||
|
)}} |
||||
|
|
||||
|
newstyle gettext looks like this instead: |
||||
|
|
||||
|
.. sourcecode:: html+jinja |
||||
|
|
||||
|
{{ gettext('Hello World!') }} |
||||
|
{{ gettext('Hello %(name)s!', name='World') }} |
||||
|
{{ ngettext('%(num)d apple', '%(num)d apples', apples|count) }} |
||||
|
|
||||
|
The advantages of newstyle gettext are that you have less to type and that |
||||
|
named placeholders become mandatory. The latter sounds like a |
||||
|
disadvantage but solves a lot of troubles translators are often facing |
||||
|
when they are unable to switch the positions of two placeholder. With |
||||
|
newstyle gettext, all format strings look the same. |
||||
|
|
||||
|
Furthermore with newstyle gettext, string formatting is also used if no |
||||
|
placeholders are used which makes all strings behave exactly the same. |
||||
|
Last but not least are newstyle gettext calls able to properly mark |
||||
|
strings for autoescaping which solves lots of escaping related issues many |
||||
|
templates are experiencing over time when using autoescaping. |
||||
|
|
||||
|
Expression Statement |
||||
|
-------------------- |
||||
|
|
||||
|
**Import name:** `jinja2.ext.do` |
||||
|
|
||||
|
The "do" aka expression-statement extension adds a simple `do` tag to the |
||||
|
template engine that works like a variable expression but ignores the |
||||
|
return value. |
||||
|
|
||||
|
.. _loopcontrols-extension: |
||||
|
|
||||
|
Loop Controls |
||||
|
------------- |
||||
|
|
||||
|
**Import name:** `jinja2.ext.loopcontrols` |
||||
|
|
||||
|
This extension adds support for `break` and `continue` in loops. After |
||||
|
enabling, Jinja2 provides those two keywords which work exactly like in |
||||
|
Python. |
||||
|
|
||||
|
.. _with-extension: |
||||
|
|
||||
|
With Statement |
||||
|
-------------- |
||||
|
|
||||
|
**Import name:** `jinja2.ext.with_` |
||||
|
|
||||
|
.. versionadded:: 2.3 |
||||
|
|
||||
|
This extension adds support for the with keyword. Using this keyword it |
||||
|
is possible to enforce a nested scope in a template. Variables can be |
||||
|
declared directly in the opening block of the with statement or using a |
||||
|
standard `set` statement directly within. |
||||
|
|
||||
|
.. _autoescape-extension: |
||||
|
|
||||
|
Autoescape Extension |
||||
|
-------------------- |
||||
|
|
||||
|
**Import name:** `jinja2.ext.autoescape` |
||||
|
|
||||
|
.. versionadded:: 2.4 |
||||
|
|
||||
|
The autoescape extension allows you to toggle the autoescape feature from |
||||
|
within the template. If the environment's :attr:`~Environment.autoescape` |
||||
|
setting is set to `False` it can be activated, if it's `True` it can be |
||||
|
deactivated. The setting overriding is scoped. |
||||
|
|
||||
|
|
||||
|
.. _writing-extensions: |
||||
|
|
||||
|
Writing Extensions |
||||
|
------------------ |
||||
|
|
||||
|
.. module:: jinja2.ext |
||||
|
|
||||
|
By writing extensions you can add custom tags to Jinja2. This is a non-trivial |
||||
|
task and usually not needed as the default tags and expressions cover all |
||||
|
common use cases. The i18n extension is a good example of why extensions are |
||||
|
useful. Another one would be fragment caching. |
||||
|
|
||||
|
When writing extensions you have to keep in mind that you are working with the |
||||
|
Jinja2 template compiler which does not validate the node tree you are passing |
||||
|
to it. If the AST is malformed you will get all kinds of compiler or runtime |
||||
|
errors that are horrible to debug. Always make sure you are using the nodes |
||||
|
you create correctly. The API documentation below shows which nodes exist and |
||||
|
how to use them. |
||||
|
|
||||
|
Example Extension |
||||
|
~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
The following example implements a `cache` tag for Jinja2 by using the |
||||
|
`Werkzeug`_ caching contrib module: |
||||
|
|
||||
|
.. literalinclude:: cache_extension.py |
||||
|
:language: python |
||||
|
|
||||
|
And here is how you use it in an environment:: |
||||
|
|
||||
|
from jinja2 import Environment |
||||
|
from werkzeug.contrib.cache import SimpleCache |
||||
|
|
||||
|
env = Environment(extensions=[FragmentCacheExtension]) |
||||
|
env.fragment_cache = SimpleCache() |
||||
|
|
||||
|
Inside the template it's then possible to mark blocks as cacheable. The |
||||
|
following example caches a sidebar for 300 seconds: |
||||
|
|
||||
|
.. sourcecode:: html+jinja |
||||
|
|
||||
|
{% cache 'sidebar', 300 %} |
||||
|
<div class="sidebar"> |
||||
|
... |
||||
|
</div> |
||||
|
{% endcache %} |
||||
|
|
||||
|
.. _Werkzeug: http://werkzeug.pocoo.org/ |
||||
|
|
||||
|
Extension API |
||||
|
~~~~~~~~~~~~~ |
||||
|
|
||||
|
Extensions always have to extend the :class:`jinja2.ext.Extension` class: |
||||
|
|
||||
|
.. autoclass:: Extension |
||||
|
:members: preprocess, filter_stream, parse, attr, call_method |
||||
|
|
||||
|
.. attribute:: identifier |
||||
|
|
||||
|
The identifier of the extension. This is always the true import name |
||||
|
of the extension class and must not be changed. |
||||
|
|
||||
|
.. attribute:: tags |
||||
|
|
||||
|
If the extension implements custom tags this is a set of tag names |
||||
|
the extension is listening for. |
||||
|
|
||||
|
Parser API |
||||
|
~~~~~~~~~~ |
||||
|
|
||||
|
The parser passed to :meth:`Extension.parse` provides ways to parse |
||||
|
expressions of different types. The following methods may be used by |
||||
|
extensions: |
||||
|
|
||||
|
.. autoclass:: jinja2.parser.Parser |
||||
|
:members: parse_expression, parse_tuple, parse_assign_target, |
||||
|
parse_statements, free_identifier, fail |
||||
|
|
||||
|
.. attribute:: filename |
||||
|
|
||||
|
The filename of the template the parser processes. This is **not** |
||||
|
the load name of the template. For the load name see :attr:`name`. |
||||
|
For templates that were not loaded form the file system this is |
||||
|
`None`. |
||||
|
|
||||
|
.. attribute:: name |
||||
|
|
||||
|
The load name of the template. |
||||
|
|
||||
|
.. attribute:: stream |
||||
|
|
||||
|
The current :class:`~jinja2.lexer.TokenStream` |
||||
|
|
||||
|
.. autoclass:: jinja2.lexer.TokenStream |
||||
|
:members: push, look, eos, skip, next, next_if, skip_if, expect |
||||
|
|
||||
|
.. attribute:: current |
||||
|
|
||||
|
The current :class:`~jinja2.lexer.Token`. |
||||
|
|
||||
|
.. autoclass:: jinja2.lexer.Token |
||||
|
:members: test, test_any |
||||
|
|
||||
|
.. attribute:: lineno |
||||
|
|
||||
|
The line number of the token |
||||
|
|
||||
|
.. attribute:: type |
||||
|
|
||||
|
The type of the token. This string is interned so you may compare |
||||
|
it with arbitrary strings using the `is` operator. |
||||
|
|
||||
|
.. attribute:: value |
||||
|
|
||||
|
The value of the token. |
||||
|
|
||||
|
There is also a utility function in the lexer module that can count newline |
||||
|
characters in strings: |
||||
|
|
||||
|
.. autofunction:: jinja2.lexer.count_newlines |
||||
|
|
||||
|
AST |
||||
|
~~~ |
||||
|
|
||||
|
The AST (Abstract Syntax Tree) is used to represent a template after parsing. |
||||
|
It's build of nodes that the compiler then converts into executable Python |
||||
|
code objects. Extensions that provide custom statements can return nodes to |
||||
|
execute custom Python code. |
||||
|
|
||||
|
The list below describes all nodes that are currently available. The AST may |
||||
|
change between Jinja2 versions but will stay backwards compatible. |
||||
|
|
||||
|
For more information have a look at the repr of :meth:`jinja2.Environment.parse`. |
||||
|
|
||||
|
.. module:: jinja2.nodes |
||||
|
|
||||
|
.. jinjanodes:: |
||||
|
|
||||
|
.. autoexception:: Impossible |
@ -0,0 +1,191 @@ |
|||||
|
Frequently Asked Questions |
||||
|
========================== |
||||
|
|
||||
|
This page answers some of the often asked questions about Jinja. |
||||
|
|
||||
|
.. highlight:: html+jinja |
||||
|
|
||||
|
Why is it called Jinja? |
||||
|
----------------------- |
||||
|
|
||||
|
The name Jinja was chosen because it's the name of a Japanese temple and |
||||
|
temple and template share a similar pronunciation. It is not named after |
||||
|
the city in Uganda. |
||||
|
|
||||
|
How fast is it? |
||||
|
--------------- |
||||
|
|
||||
|
We really hate benchmarks especially since they don't reflect much. The |
||||
|
performance of a template depends on many factors and you would have to |
||||
|
benchmark different engines in different situations. The benchmarks from the |
||||
|
testsuite show that Jinja2 has a similar performance to `Mako`_ and is between |
||||
|
10 and 20 times faster than Django's template engine or Genshi. These numbers |
||||
|
should be taken with tons of salt as the benchmarks that took these numbers |
||||
|
only test a few performance related situations such as looping. Generally |
||||
|
speaking the performance of a template engine doesn't matter much as the |
||||
|
usual bottleneck in a web application is either the database or the application |
||||
|
code. |
||||
|
|
||||
|
.. _Mako: http://www.makotemplates.org/ |
||||
|
|
||||
|
How Compatible is Jinja2 with Django? |
||||
|
------------------------------------- |
||||
|
|
||||
|
The default syntax of Jinja2 matches Django syntax in many ways. However |
||||
|
this similarity doesn't mean that you can use a Django template unmodified |
||||
|
in Jinja2. For example filter arguments use a function call syntax rather |
||||
|
than a colon to separate filter name and arguments. Additionally the |
||||
|
extension interface in Jinja is fundamentally different from the Django one |
||||
|
which means that your custom tags won't work any longer. |
||||
|
|
||||
|
Generally speaking you will use much less custom extensions as the Jinja |
||||
|
template system allows you to use a certain subset of Python expressions |
||||
|
which can replace most Django extensions. For example instead of using |
||||
|
something like this:: |
||||
|
|
||||
|
{% load comments %} |
||||
|
{% get_latest_comments 10 as latest_comments %} |
||||
|
{% for comment in latest_comments %} |
||||
|
... |
||||
|
{% endfor %} |
||||
|
|
||||
|
You will most likely provide an object with attributes to retrieve |
||||
|
comments from the database:: |
||||
|
|
||||
|
{% for comment in models.comments.latest(10) %} |
||||
|
... |
||||
|
{% endfor %} |
||||
|
|
||||
|
Or directly provide the model for quick testing:: |
||||
|
|
||||
|
{% for comment in Comment.objects.order_by('-pub_date')[:10] %} |
||||
|
... |
||||
|
{% endfor %} |
||||
|
|
||||
|
Please keep in mind that even though you may put such things into templates |
||||
|
it still isn't a good idea. Queries should go into the view code and not |
||||
|
the template! |
||||
|
|
||||
|
Isn't it a terrible idea to put Logic into Templates? |
||||
|
----------------------------------------------------- |
||||
|
|
||||
|
Without a doubt you should try to remove as much logic from templates as |
||||
|
possible. But templates without any logic mean that you have to do all |
||||
|
the processing in the code which is boring and stupid. A template engine |
||||
|
that does that is shipped with Python and called `string.Template`. Comes |
||||
|
without loops and if conditions and is by far the fastest template engine |
||||
|
you can get for Python. |
||||
|
|
||||
|
So some amount of logic is required in templates to keep everyone happy. |
||||
|
And Jinja leaves it pretty much to you how much logic you want to put into |
||||
|
templates. There are some restrictions in what you can do and what not. |
||||
|
|
||||
|
Jinja2 neither allows you to put arbitrary Python code into templates nor |
||||
|
does it allow all Python expressions. The operators are limited to the |
||||
|
most common ones and more advanced expressions such as list comprehensions |
||||
|
and generator expressions are not supported. This keeps the template engine |
||||
|
easier to maintain and templates more readable. |
||||
|
|
||||
|
Why is Autoescaping not the Default? |
||||
|
------------------------------------ |
||||
|
|
||||
|
There are multiple reasons why automatic escaping is not the default mode |
||||
|
and also not the recommended one. While automatic escaping of variables |
||||
|
means that you will less likely have an XSS problem it also causes a huge |
||||
|
amount of extra processing in the template engine which can cause serious |
||||
|
performance problems. As Python doesn't provide a way to mark strings as |
||||
|
unsafe Jinja has to hack around that limitation by providing a custom |
||||
|
string class (the :class:`Markup` string) that safely interacts with safe |
||||
|
and unsafe strings. |
||||
|
|
||||
|
With explicit escaping however the template engine doesn't have to perform |
||||
|
any safety checks on variables. Also a human knows not to escape integers |
||||
|
or strings that may never contain characters one has to escape or already |
||||
|
HTML markup. For example when iterating over a list over a table of |
||||
|
integers and floats for a table of statistics the template designer can |
||||
|
omit the escaping because he knows that integers or floats don't contain |
||||
|
any unsafe parameters. |
||||
|
|
||||
|
Additionally Jinja2 is a general purpose template engine and not only used |
||||
|
for HTML/XML generation. For example you may generate LaTeX, emails, |
||||
|
CSS, JavaScript, or configuration files. |
||||
|
|
||||
|
Why is the Context immutable? |
||||
|
----------------------------- |
||||
|
|
||||
|
When writing a :func:`contextfunction` or something similar you may have |
||||
|
noticed that the context tries to stop you from modifying it. If you have |
||||
|
managed to modify the context by using an internal context API you may |
||||
|
have noticed that changes in the context don't seem to be visible in the |
||||
|
template. The reason for this is that Jinja uses the context only as |
||||
|
primary data source for template variables for performance reasons. |
||||
|
|
||||
|
If you want to modify the context write a function that returns a variable |
||||
|
instead that one can assign to a variable by using set:: |
||||
|
|
||||
|
{% set comments = get_latest_comments() %} |
||||
|
|
||||
|
My tracebacks look weird. What's happening? |
||||
|
-------------------------------------------- |
||||
|
|
||||
|
If the debugsupport module is not compiled and you are using a Python |
||||
|
installation without ctypes (Python 2.4 without ctypes, Jython or Google's |
||||
|
AppEngine) Jinja2 is unable to provide correct debugging information and |
||||
|
the traceback may be incomplete. There is currently no good workaround |
||||
|
for Jython or the AppEngine as ctypes is unavailable there and it's not |
||||
|
possible to use the debugsupport extension. |
||||
|
|
||||
|
If you are working in the Google AppEngine development server you can |
||||
|
whitelist the ctypes module to restore the tracebacks. This however won't |
||||
|
work in production environments:: |
||||
|
|
||||
|
import os |
||||
|
if os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'): |
||||
|
from google.appengine.tools.dev_appserver import HardenedModulesHook |
||||
|
HardenedModulesHook._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt'] |
||||
|
|
||||
|
Credit for this snippet goes to `Thomas Johansson |
||||
|
<http://stackoverflow.com/questions/3086091/debug-jinja2-in-google-app-engine/3694434#3694434>`_ |
||||
|
|
||||
|
Why is there no Python 2.3/2.4/2.5/3.1/3.2 support? |
||||
|
--------------------------------------------------- |
||||
|
|
||||
|
Python 2.3 is missing a lot of features that are used heavily in Jinja2. This |
||||
|
decision was made as with the upcoming Python 2.6 and 3.0 versions it becomes |
||||
|
harder to maintain the code for older Python versions. If you really need |
||||
|
Python 2.3 support you either have to use `Jinja 1`_ or other templating |
||||
|
engines that still support 2.3. |
||||
|
|
||||
|
Python 2.4/2.5/3.1/3.2 support was removed when we switched to supporting |
||||
|
Python 2 and 3 by the same sourcecode (without using 2to3). It was required to |
||||
|
drop support because only Python 2.6/2.7 and >=3.3 support byte and unicode |
||||
|
literals in a way compatible to each other version. If you really need support |
||||
|
for older Python 2 (or 3) versions, you can just use Jinja2 2.6. |
||||
|
|
||||
|
My Macros are overridden by something |
||||
|
------------------------------------- |
||||
|
|
||||
|
In some situations the Jinja scoping appears arbitrary: |
||||
|
|
||||
|
layout.tmpl: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{% macro foo() %}LAYOUT{% endmacro %} |
||||
|
{% block body %}{% endblock %} |
||||
|
|
||||
|
child.tmpl: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{% extends 'layout.tmpl' %} |
||||
|
{% macro foo() %}CHILD{% endmacro %} |
||||
|
{% block body %}{{ foo() }}{% endblock %} |
||||
|
|
||||
|
This will print ``LAYOUT`` in Jinja2. This is a side effect of having |
||||
|
the parent template evaluated after the child one. This allows child |
||||
|
templates passing information to the parent template. To avoid this |
||||
|
issue rename the macro or variable in the parent template to have an |
||||
|
uncommon prefix. |
||||
|
|
||||
|
.. _Jinja 1: http://jinja.pocoo.org/1/ |
@ -0,0 +1,34 @@ |
|||||
|
Welcome to Jinja2 |
||||
|
================= |
||||
|
|
||||
|
Jinja2 is a modern and designer-friendly templating language for Python, |
||||
|
modelled after Django's templates. It is fast, widely used and secure |
||||
|
with the optional sandboxed template execution environment: |
||||
|
|
||||
|
.. sourcecode:: html+jinja |
||||
|
|
||||
|
<title>{% block title %}{% endblock %}</title> |
||||
|
<ul> |
||||
|
{% for user in users %} |
||||
|
<li><a href="{{ user.url }}">{{ user.username }}</a></li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
|
||||
|
**Features:** |
||||
|
|
||||
|
- sandboxed execution |
||||
|
- powerful automatic HTML escaping system for XSS prevention |
||||
|
- template inheritance |
||||
|
- compiles down to the optimal python code just in time |
||||
|
- optional ahead-of-time template compilation |
||||
|
- easy to debug. Line numbers of exceptions directly point to |
||||
|
the correct line in the template. |
||||
|
- configurable syntax |
||||
|
|
||||
|
.. include:: contents.rst.inc |
||||
|
|
||||
|
If you can't find the information you're looking for, have a look at the |
||||
|
index or try to find it using the search function: |
||||
|
|
||||
|
* :ref:`genindex` |
||||
|
* :ref:`search` |
@ -0,0 +1,101 @@ |
|||||
|
Integration |
||||
|
=========== |
||||
|
|
||||
|
Jinja2 provides some code for integration into other tools such as frameworks, |
||||
|
the `Babel`_ library or your favourite editor for fancy code highlighting. |
||||
|
This is a brief description of whats included. |
||||
|
|
||||
|
Files to help integration are available |
||||
|
`here. <https://github.com/mitsuhiko/jinja2/tree/master/ext>`_ |
||||
|
|
||||
|
.. _babel-integration: |
||||
|
|
||||
|
Babel Integration |
||||
|
----------------- |
||||
|
|
||||
|
Jinja provides support for extracting gettext messages from templates via a |
||||
|
`Babel`_ extractor entry point called `jinja2.ext.babel_extract`. The Babel |
||||
|
support is implemented as part of the :ref:`i18n-extension` extension. |
||||
|
|
||||
|
Gettext messages extracted from both `trans` tags and code expressions. |
||||
|
|
||||
|
To extract gettext messages from templates, the project needs a Jinja2 section |
||||
|
in its Babel extraction method `mapping file`_: |
||||
|
|
||||
|
.. sourcecode:: ini |
||||
|
|
||||
|
[jinja2: **/templates/**.html] |
||||
|
encoding = utf-8 |
||||
|
|
||||
|
The syntax related options of the :class:`Environment` are also available as |
||||
|
configuration values in the mapping file. For example to tell the extraction |
||||
|
that templates use ``%`` as `line_statement_prefix` you can use this code: |
||||
|
|
||||
|
.. sourcecode:: ini |
||||
|
|
||||
|
[jinja2: **/templates/**.html] |
||||
|
encoding = utf-8 |
||||
|
line_statement_prefix = % |
||||
|
|
||||
|
:ref:`jinja-extensions` may also be defined by passing a comma separated list |
||||
|
of import paths as `extensions` value. The i18n extension is added |
||||
|
automatically. |
||||
|
|
||||
|
.. versionchanged:: 2.7 |
||||
|
|
||||
|
Until 2.7 template syntax errors were always ignored. This was done |
||||
|
since many people are dropping non template html files into the |
||||
|
templates folder and it would randomly fail. The assumption was that |
||||
|
testsuites will catch syntax errors in templates anyways. If you don't |
||||
|
want that behavior you can add ``silent=false`` to the settings and |
||||
|
exceptions are propagated. |
||||
|
|
||||
|
.. _mapping file: http://babel.edgewall.org/wiki/Documentation/messages.html#extraction-method-mapping-and-configuration |
||||
|
|
||||
|
Pylons |
||||
|
------ |
||||
|
|
||||
|
With `Pylons`_ 0.9.7 onwards it's incredible easy to integrate Jinja into a |
||||
|
Pylons powered application. |
||||
|
|
||||
|
The template engine is configured in `config/environment.py`. The configuration |
||||
|
for Jinja2 looks something like that:: |
||||
|
|
||||
|
from jinja2 import Environment, PackageLoader |
||||
|
config['pylons.app_globals'].jinja_env = Environment( |
||||
|
loader=PackageLoader('yourapplication', 'templates') |
||||
|
) |
||||
|
|
||||
|
After that you can render Jinja templates by using the `render_jinja` function |
||||
|
from the `pylons.templating` module. |
||||
|
|
||||
|
Additionally it's a good idea to set the Pylons' `c` object into strict mode. |
||||
|
Per default any attribute to not existing attributes on the `c` object return |
||||
|
an empty string and not an undefined object. To change this just use this |
||||
|
snippet and add it into your `config/environment.py`:: |
||||
|
|
||||
|
config['pylons.strict_c'] = True |
||||
|
|
||||
|
.. _Pylons: http://www.pylonshq.com/ |
||||
|
|
||||
|
TextMate |
||||
|
-------- |
||||
|
|
||||
|
There is a bundle for TextMate that supports syntax highlighting for Jinja1 and Jinja2 for text based |
||||
|
templates as well as HTML. It also contains a few often used snippets. |
||||
|
|
||||
|
.. _TextMate Bundle: https://github.com/mitsuhiko/jinja2-tmbundle |
||||
|
|
||||
|
Vim |
||||
|
--- |
||||
|
|
||||
|
A syntax plugin for `Vim`_ exists in the Vim-scripts directory as well as the |
||||
|
`ext` folder at the root of the Jinja2 project. `The script |
||||
|
<http://www.vim.org/scripts/script.php?script_id=1856>`_ supports Jinja1 and |
||||
|
Jinja2. Once installed two file types are available `jinja` and `htmljinja`. |
||||
|
The first one for text based templates, the latter for HTML templates. |
||||
|
|
||||
|
Copy the files into your `syntax` folder. |
||||
|
|
||||
|
.. _Babel: http://babel.edgewall.org/ |
||||
|
.. _Vim: http://www.vim.org/ |
@ -0,0 +1,123 @@ |
|||||
|
Introduction |
||||
|
============ |
||||
|
|
||||
|
This is the documentation for the Jinja2 general purpose templating language. |
||||
|
Jinja2 is a library for Python that is designed to be flexible, fast and secure. |
||||
|
|
||||
|
If you have any exposure to other text-based template languages, such as Smarty or |
||||
|
Django, you should feel right at home with Jinja2. It's both designer and |
||||
|
developer friendly by sticking to Python's principles and adding functionality |
||||
|
useful for templating environments. |
||||
|
|
||||
|
Prerequisites |
||||
|
------------- |
||||
|
|
||||
|
Jinja2 works with Python 2.6.x, 2.7.x and >= 3.3. If you are using Python |
||||
|
3.2 you can use an older release of Jinja2 (2.6) as support for Python 3.2 |
||||
|
was dropped in Jinja2 version 2.7. |
||||
|
|
||||
|
If you wish to use the :class:`~jinja2.PackageLoader` class, you will also |
||||
|
need `setuptools`_ or `distribute`_ installed at runtime. |
||||
|
|
||||
|
Installation |
||||
|
------------ |
||||
|
|
||||
|
You have multiple ways to install Jinja2. If you are unsure what to do, go |
||||
|
with the Python egg or tarball. |
||||
|
|
||||
|
As a Python egg (via `easy_install`) |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
You can install the most recent Jinja2 version using `easy_install`_ or `pip`_:: |
||||
|
|
||||
|
easy_install Jinja2 |
||||
|
pip install Jinja2 |
||||
|
|
||||
|
This will install a Jinja2 egg in your Python installation's site-packages |
||||
|
directory. |
||||
|
|
||||
|
(If you are installing from the Windows command line omit the `sudo` and make |
||||
|
sure to run the command as user with administrator rights) |
||||
|
|
||||
|
From the tarball release |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
1. Download the most recent tarball from the `download page`_ |
||||
|
2. Unpack the tarball |
||||
|
3. ``sudo python setup.py install`` |
||||
|
|
||||
|
Note that you either have to have `setuptools` or `distribute` installed; |
||||
|
the latter is preferred. |
||||
|
|
||||
|
This will install Jinja2 into your Python installation's site-packages directory. |
||||
|
|
||||
|
Installing the development version |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
1. Install `git`_ |
||||
|
2. ``git clone git://github.com/mitsuhiko/jinja2.git`` |
||||
|
3. ``cd jinja2`` |
||||
|
4. ``ln -s jinja2 /usr/lib/python2.X/site-packages`` |
||||
|
|
||||
|
As an alternative to steps 4 you can also do ``python setup.py develop`` |
||||
|
which will install the package via `distribute` in development mode. This also |
||||
|
has the advantage that the C extensions are compiled. |
||||
|
|
||||
|
.. _download page: http://pypi.python.org/pypi/Jinja2 |
||||
|
.. _distribute: http://pypi.python.org/pypi/distribute |
||||
|
.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools |
||||
|
.. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall |
||||
|
.. _pip: http://pypi.python.org/pypi/pip |
||||
|
.. _git: http://git-scm.org/ |
||||
|
|
||||
|
|
||||
|
MarkupSafe Dependency |
||||
|
~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
As of version 2.7 Jinja2 depends on the `MarkupSafe`_ module. If you |
||||
|
install Jinja2 via `pip` or `easy_install` it will be installed |
||||
|
automatically for you. |
||||
|
|
||||
|
.. _MarkupSafe: http://pypi.python.org/pypi/MarkupSafe |
||||
|
|
||||
|
Basic API Usage |
||||
|
--------------- |
||||
|
|
||||
|
This section gives you a brief introduction to the Python API for Jinja2 |
||||
|
templates. |
||||
|
|
||||
|
The most basic way to create a template and render it is through |
||||
|
:class:`~jinja2.Template`. This however is not the recommended way to |
||||
|
work with it if your templates are not loaded from strings but the file |
||||
|
system or another data source: |
||||
|
|
||||
|
>>> from jinja2 import Template |
||||
|
>>> template = Template('Hello {{ name }}!') |
||||
|
>>> template.render(name='John Doe') |
||||
|
u'Hello John Doe!' |
||||
|
|
||||
|
By creating an instance of :class:`~jinja2.Template` you get back a new template |
||||
|
object that provides a method called :meth:`~jinja2.Template.render` which when |
||||
|
called with a dict or keyword arguments expands the template. The dict |
||||
|
or keywords arguments passed to the template are the so-called "context" |
||||
|
of the template. |
||||
|
|
||||
|
What you can see here is that Jinja2 is using unicode internally and the |
||||
|
return value is an unicode string. So make sure that your application is |
||||
|
indeed using unicode internally. |
||||
|
|
||||
|
|
||||
|
Experimental Python 3 Support |
||||
|
----------------------------- |
||||
|
|
||||
|
Jinja 2.7 brings experimental support for Python >=3.3. It means that all |
||||
|
unittests pass on the new version, but there might still be small bugs in |
||||
|
there and behavior might be inconsistent. If you notice any bugs, please |
||||
|
provide feedback in the `Jinja bug tracker`_. |
||||
|
|
||||
|
Also please keep in mind that the documentation is written with Python 2 |
||||
|
in mind, so you will have to adapt the shown code examples to Python 3 syntax |
||||
|
for yourself. |
||||
|
|
||||
|
|
||||
|
.. _Jinja bug tracker: http://github.com/mitsuhiko/jinja2/issues |
@ -0,0 +1,194 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
Jinja Documentation Extensions |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Support for automatically documenting filters and tests. |
||||
|
|
||||
|
:copyright: Copyright 2008 by Armin Ronacher. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
import collections |
||||
|
import os |
||||
|
import re |
||||
|
import inspect |
||||
|
import jinja2 |
||||
|
from itertools import islice |
||||
|
from types import BuiltinFunctionType |
||||
|
from docutils import nodes |
||||
|
from docutils.statemachine import ViewList |
||||
|
from sphinx.ext.autodoc import prepare_docstring |
||||
|
from sphinx.application import TemplateBridge |
||||
|
from pygments.style import Style |
||||
|
from pygments.token import Keyword, Name, Comment, String, Error, \ |
||||
|
Number, Operator, Generic |
||||
|
from jinja2 import Environment, FileSystemLoader |
||||
|
|
||||
|
|
||||
|
def parse_rst(state, content_offset, doc): |
||||
|
node = nodes.section() |
||||
|
# hack around title style bookkeeping |
||||
|
surrounding_title_styles = state.memo.title_styles |
||||
|
surrounding_section_level = state.memo.section_level |
||||
|
state.memo.title_styles = [] |
||||
|
state.memo.section_level = 0 |
||||
|
state.nested_parse(doc, content_offset, node, match_titles=1) |
||||
|
state.memo.title_styles = surrounding_title_styles |
||||
|
state.memo.section_level = surrounding_section_level |
||||
|
return node.children |
||||
|
|
||||
|
|
||||
|
class JinjaStyle(Style): |
||||
|
title = 'Jinja Style' |
||||
|
default_style = "" |
||||
|
styles = { |
||||
|
Comment: 'italic #aaaaaa', |
||||
|
Comment.Preproc: 'noitalic #B11414', |
||||
|
Comment.Special: 'italic #505050', |
||||
|
|
||||
|
Keyword: 'bold #B80000', |
||||
|
Keyword.Type: '#808080', |
||||
|
|
||||
|
Operator.Word: 'bold #B80000', |
||||
|
|
||||
|
Name.Builtin: '#333333', |
||||
|
Name.Function: '#333333', |
||||
|
Name.Class: 'bold #333333', |
||||
|
Name.Namespace: 'bold #333333', |
||||
|
Name.Entity: 'bold #363636', |
||||
|
Name.Attribute: '#686868', |
||||
|
Name.Tag: 'bold #686868', |
||||
|
Name.Decorator: '#686868', |
||||
|
|
||||
|
String: '#AA891C', |
||||
|
Number: '#444444', |
||||
|
|
||||
|
Generic.Heading: 'bold #000080', |
||||
|
Generic.Subheading: 'bold #800080', |
||||
|
Generic.Deleted: '#aa0000', |
||||
|
Generic.Inserted: '#00aa00', |
||||
|
Generic.Error: '#aa0000', |
||||
|
Generic.Emph: 'italic', |
||||
|
Generic.Strong: 'bold', |
||||
|
Generic.Prompt: '#555555', |
||||
|
Generic.Output: '#888888', |
||||
|
Generic.Traceback: '#aa0000', |
||||
|
|
||||
|
Error: '#F00 bg:#FAA' |
||||
|
} |
||||
|
|
||||
|
|
||||
|
_sig_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*(\(.*?\))') |
||||
|
|
||||
|
|
||||
|
def format_function(name, aliases, func): |
||||
|
lines = inspect.getdoc(func).splitlines() |
||||
|
signature = '()' |
||||
|
if isinstance(func, BuiltinFunctionType): |
||||
|
match = _sig_re.match(lines[0]) |
||||
|
if match is not None: |
||||
|
del lines[:1 + bool(lines and not lines[0])] |
||||
|
signature = match.group(1) |
||||
|
else: |
||||
|
try: |
||||
|
argspec = inspect.getargspec(func) |
||||
|
if getattr(func, 'environmentfilter', False) or \ |
||||
|
getattr(func, 'contextfilter', False) or \ |
||||
|
getattr(func, 'evalcontextfilter', False): |
||||
|
del argspec[0][0] |
||||
|
signature = inspect.formatargspec(*argspec) |
||||
|
except: |
||||
|
pass |
||||
|
result = ['.. function:: %s%s' % (name, signature), ''] |
||||
|
result.extend(' ' + line for line in lines) |
||||
|
if aliases: |
||||
|
result.extend(('', ' :aliases: %s' % ', '.join( |
||||
|
'``%s``' % x for x in sorted(aliases)))) |
||||
|
return result |
||||
|
|
||||
|
|
||||
|
def dump_functions(mapping): |
||||
|
def directive(dirname, arguments, options, content, lineno, |
||||
|
content_offset, block_text, state, state_machine): |
||||
|
reverse_mapping = {} |
||||
|
for name, func in mapping.items(): |
||||
|
reverse_mapping.setdefault(func, []).append(name) |
||||
|
filters = [] |
||||
|
for func, names in reverse_mapping.items(): |
||||
|
aliases = sorted(names, key=lambda x: len(x)) |
||||
|
name = aliases.pop() |
||||
|
filters.append((name, aliases, func)) |
||||
|
filters.sort() |
||||
|
|
||||
|
result = ViewList() |
||||
|
for name, aliases, func in filters: |
||||
|
for item in format_function(name, aliases, func): |
||||
|
result.append(item, '<jinjaext>') |
||||
|
|
||||
|
node = nodes.paragraph() |
||||
|
state.nested_parse(result, content_offset, node) |
||||
|
return node.children |
||||
|
return directive |
||||
|
|
||||
|
|
||||
|
from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS |
||||
|
jinja_filters = dump_functions(DEFAULT_FILTERS) |
||||
|
jinja_tests = dump_functions(DEFAULT_TESTS) |
||||
|
|
||||
|
|
||||
|
def jinja_nodes(dirname, arguments, options, content, lineno, |
||||
|
content_offset, block_text, state, state_machine): |
||||
|
from jinja2.nodes import Node |
||||
|
doc = ViewList() |
||||
|
def walk(node, indent): |
||||
|
p = ' ' * indent |
||||
|
sig = ', '.join(node.fields) |
||||
|
doc.append(p + '.. autoclass:: %s(%s)' % (node.__name__, sig), '') |
||||
|
if node.abstract: |
||||
|
members = [] |
||||
|
for key, name in node.__dict__.items(): |
||||
|
if not key.startswith('_') and \ |
||||
|
not hasattr(node.__base__, key) and isinstance(name, collections.Callable): |
||||
|
members.append(key) |
||||
|
if members: |
||||
|
members.sort() |
||||
|
doc.append('%s :members: %s' % (p, ', '.join(members)), '') |
||||
|
if node.__base__ != object: |
||||
|
doc.append('', '') |
||||
|
doc.append('%s :Node type: :class:`%s`' % |
||||
|
(p, node.__base__.__name__), '') |
||||
|
doc.append('', '') |
||||
|
children = node.__subclasses__() |
||||
|
children.sort(key=lambda x: x.__name__.lower()) |
||||
|
for child in children: |
||||
|
walk(child, indent) |
||||
|
walk(Node, 0) |
||||
|
return parse_rst(state, content_offset, doc) |
||||
|
|
||||
|
|
||||
|
def inject_toc(app, doctree, docname): |
||||
|
titleiter = iter(doctree.traverse(nodes.title)) |
||||
|
try: |
||||
|
# skip first title, we are not interested in that one |
||||
|
next(titleiter) |
||||
|
title = next(titleiter) |
||||
|
# and check if there is at least another title |
||||
|
next(titleiter) |
||||
|
except StopIteration: |
||||
|
return |
||||
|
tocnode = nodes.section('') |
||||
|
tocnode['classes'].append('toc') |
||||
|
toctitle = nodes.section('') |
||||
|
toctitle['classes'].append('toctitle') |
||||
|
toctitle.append(nodes.title(text='Table Of Contents')) |
||||
|
tocnode.append(toctitle) |
||||
|
tocnode += doctree.document.settings.env.get_toc_for(docname)[0][1] |
||||
|
title.parent.insert(title.parent.children.index(title), tocnode) |
||||
|
|
||||
|
|
||||
|
def setup(app): |
||||
|
app.add_directive('jinjafilters', jinja_filters, 0, (0, 0, 0)) |
||||
|
app.add_directive('jinjatests', jinja_tests, 0, (0, 0, 0)) |
||||
|
app.add_directive('jinjanodes', jinja_nodes, 0, (0, 0, 0)) |
||||
|
# uncomment for inline toc. links are broken unfortunately |
||||
|
##app.connect('doctree-resolved', inject_toc) |
@ -0,0 +1,119 @@ |
|||||
|
\definecolor{TitleColor}{rgb}{0,0,0} |
||||
|
\definecolor{InnerLinkColor}{rgb}{0,0,0} |
||||
|
\definecolor{OuterLinkColor}{rgb}{0.8,0,0} |
||||
|
|
||||
|
\renewcommand{\maketitle}{% |
||||
|
\begin{titlepage}% |
||||
|
\let\footnotesize\small |
||||
|
\let\footnoterule\relax |
||||
|
\ifsphinxpdfoutput |
||||
|
\begingroup |
||||
|
% This \def is required to deal with multi-line authors; it |
||||
|
% changes \\ to ', ' (comma-space), making it pass muster for |
||||
|
% generating document info in the PDF file. |
||||
|
\def\\{, } |
||||
|
\pdfinfo{ |
||||
|
/Author (\@author) |
||||
|
/Title (\@title) |
||||
|
} |
||||
|
\endgroup |
||||
|
\fi |
||||
|
\begin{flushright}% |
||||
|
%\sphinxlogo% |
||||
|
{\center |
||||
|
\vspace*{3cm} |
||||
|
\includegraphics{logo.pdf} |
||||
|
\vspace{3cm} |
||||
|
\par |
||||
|
{\rm\Huge \@title \par}% |
||||
|
{\em\LARGE \py@release\releaseinfo \par} |
||||
|
{\large |
||||
|
\@date \par |
||||
|
\py@authoraddress \par |
||||
|
}}% |
||||
|
\end{flushright}%\par |
||||
|
\@thanks |
||||
|
\end{titlepage}% |
||||
|
\cleardoublepage% |
||||
|
\setcounter{footnote}{0}% |
||||
|
\let\thanks\relax\let\maketitle\relax |
||||
|
%\gdef\@thanks{}\gdef\@author{}\gdef\@title{} |
||||
|
} |
||||
|
|
||||
|
\fancypagestyle{normal}{ |
||||
|
\fancyhf{} |
||||
|
\fancyfoot[LE,RO]{{\thepage}} |
||||
|
\fancyfoot[LO]{{\nouppercase{\rightmark}}} |
||||
|
\fancyfoot[RE]{{\nouppercase{\leftmark}}} |
||||
|
\fancyhead[LE,RO]{{ \@title, \py@release}} |
||||
|
\renewcommand{\headrulewidth}{0.4pt} |
||||
|
\renewcommand{\footrulewidth}{0.4pt} |
||||
|
} |
||||
|
|
||||
|
\fancypagestyle{plain}{ |
||||
|
\fancyhf{} |
||||
|
\fancyfoot[LE,RO]{{\thepage}} |
||||
|
\renewcommand{\headrulewidth}{0pt} |
||||
|
\renewcommand{\footrulewidth}{0.4pt} |
||||
|
} |
||||
|
|
||||
|
\titleformat{\section}{\Large}% |
||||
|
{\py@TitleColor\thesection}{0.5em}{\py@TitleColor}{\py@NormalColor} |
||||
|
\titleformat{\subsection}{\large}% |
||||
|
{\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} |
||||
|
\titleformat{\subsubsection}{}% |
||||
|
{\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} |
||||
|
\titleformat{\paragraph}{\large}% |
||||
|
{\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor} |
||||
|
|
||||
|
\ChNameVar{\raggedleft\normalsize} |
||||
|
\ChNumVar{\raggedleft \bfseries\Large} |
||||
|
\ChTitleVar{\raggedleft \rm\Huge} |
||||
|
|
||||
|
\renewcommand\thepart{\@Roman\c@part} |
||||
|
\renewcommand\part{% |
||||
|
\pagestyle{plain} |
||||
|
\if@noskipsec \leavevmode \fi |
||||
|
\cleardoublepage |
||||
|
\vspace*{6cm}% |
||||
|
\@afterindentfalse |
||||
|
\secdef\@part\@spart} |
||||
|
|
||||
|
\def\@part[#1]#2{% |
||||
|
\ifnum \c@secnumdepth >\m@ne |
||||
|
\refstepcounter{part}% |
||||
|
\addcontentsline{toc}{part}{\thepart\hspace{1em}#1}% |
||||
|
\else |
||||
|
\addcontentsline{toc}{part}{#1}% |
||||
|
\fi |
||||
|
{\parindent \z@ %\center |
||||
|
\interlinepenalty \@M |
||||
|
\normalfont |
||||
|
\ifnum \c@secnumdepth >\m@ne |
||||
|
\rm\Large \partname~\thepart |
||||
|
\par\nobreak |
||||
|
\fi |
||||
|
\MakeUppercase{\rm\Huge #2}% |
||||
|
\markboth{}{}\par}% |
||||
|
\nobreak |
||||
|
\vskip 8ex |
||||
|
\@afterheading} |
||||
|
\def\@spart#1{% |
||||
|
{\parindent \z@ %\center |
||||
|
\interlinepenalty \@M |
||||
|
\normalfont |
||||
|
\huge \bfseries #1\par}% |
||||
|
\nobreak |
||||
|
\vskip 3ex |
||||
|
\@afterheading} |
||||
|
|
||||
|
% use inconsolata font |
||||
|
\usepackage{inconsolata} |
||||
|
|
||||
|
% fix single quotes, for inconsolata. (does not work) |
||||
|
%%\usepackage{textcomp} |
||||
|
%%\begingroup |
||||
|
%% \catcode`'=\active |
||||
|
%% \g@addto@macro\@noligs{\let'\textsinglequote} |
||||
|
%% \endgroup |
||||
|
%%\endinput |
@ -0,0 +1,6 @@ |
|||||
|
:orphan: |
||||
|
|
||||
|
Jinja2 Documentation |
||||
|
==================== |
||||
|
|
||||
|
.. include:: contents.rst.inc |
Binary file not shown.
@ -0,0 +1,94 @@ |
|||||
|
Sandbox |
||||
|
======= |
||||
|
|
||||
|
The Jinja2 sandbox can be used to evaluate untrusted code. Access to unsafe |
||||
|
attributes and methods is prohibited. |
||||
|
|
||||
|
Assuming `env` is a :class:`SandboxedEnvironment` in the default configuration |
||||
|
the following piece of code shows how it works: |
||||
|
|
||||
|
>>> env.from_string("{{ func.func_code }}").render(func=lambda:None) |
||||
|
u'' |
||||
|
>>> env.from_string("{{ func.func_code.do_something }}").render(func=lambda:None) |
||||
|
Traceback (most recent call last): |
||||
|
... |
||||
|
SecurityError: access to attribute 'func_code' of 'function' object is unsafe. |
||||
|
|
||||
|
API |
||||
|
--- |
||||
|
|
||||
|
.. module:: jinja2.sandbox |
||||
|
|
||||
|
.. autoclass:: SandboxedEnvironment([options]) |
||||
|
:members: is_safe_attribute, is_safe_callable, default_binop_table, |
||||
|
default_unop_table, intercepted_binops, intercepted_unops, |
||||
|
call_binop, call_unop |
||||
|
|
||||
|
.. autoclass:: ImmutableSandboxedEnvironment([options]) |
||||
|
|
||||
|
.. autoexception:: SecurityError |
||||
|
|
||||
|
.. autofunction:: unsafe |
||||
|
|
||||
|
.. autofunction:: is_internal_attribute |
||||
|
|
||||
|
.. autofunction:: modifies_known_mutable |
||||
|
|
||||
|
.. admonition:: Note |
||||
|
|
||||
|
The Jinja2 sandbox alone is no solution for perfect security. Especially |
||||
|
for web applications you have to keep in mind that users may create |
||||
|
templates with arbitrary HTML in so it's crucial to ensure that (if you |
||||
|
are running multiple users on the same server) they can't harm each other |
||||
|
via JavaScript insertions and much more. |
||||
|
|
||||
|
Also the sandbox is only as good as the configuration. We strongly |
||||
|
recommend only passing non-shared resources to the template and use |
||||
|
some sort of whitelisting for attributes. |
||||
|
|
||||
|
Also keep in mind that templates may raise runtime or compile time errors, |
||||
|
so make sure to catch them. |
||||
|
|
||||
|
Operator Intercepting |
||||
|
--------------------- |
||||
|
|
||||
|
.. versionadded:: 2.6 |
||||
|
|
||||
|
For maximum performance Jinja2 will let operators call directly the type |
||||
|
specific callback methods. This means that it's not possible to have this |
||||
|
intercepted by overriding :meth:`Environment.call`. Furthermore a |
||||
|
conversion from operator to special method is not always directly possible |
||||
|
due to how operators work. For instance for divisions more than one |
||||
|
special method exist. |
||||
|
|
||||
|
With Jinja 2.6 there is now support for explicit operator intercepting. |
||||
|
This can be used to customize specific operators as necessary. In order |
||||
|
to intercept an operator one has to override the |
||||
|
:attr:`SandboxedEnvironment.intercepted_binops` attribute. Once the |
||||
|
operator that needs to be intercepted is added to that set Jinja2 will |
||||
|
generate bytecode that calls the :meth:`SandboxedEnvironment.call_binop` |
||||
|
function. For unary operators the `unary` attributes and methods have to |
||||
|
be used instead. |
||||
|
|
||||
|
The default implementation of :attr:`SandboxedEnvironment.call_binop` |
||||
|
will use the :attr:`SandboxedEnvironment.binop_table` to translate |
||||
|
operator symbols into callbacks performing the default operator behavior. |
||||
|
|
||||
|
This example shows how the power (``**``) operator can be disabled in |
||||
|
Jinja2:: |
||||
|
|
||||
|
from jinja2.sandbox import SandboxedEnvironment |
||||
|
|
||||
|
|
||||
|
class MyEnvironment(SandboxedEnvironment): |
||||
|
intercepted_binops = frozenset(['**']) |
||||
|
|
||||
|
def call_binop(self, context, operator, left, right): |
||||
|
if operator == '**': |
||||
|
return self.undefined('the power operator is unavailable') |
||||
|
return SandboxedEnvironment.call_binop(self, context, |
||||
|
operator, left, right) |
||||
|
|
||||
|
Make sure to always call into the super method, even if you are not |
||||
|
intercepting the call. Jinja2 might internally call the method to |
||||
|
evaluate expressions. |
@ -0,0 +1,226 @@ |
|||||
|
Switching from other Template Engines |
||||
|
===================================== |
||||
|
|
||||
|
.. highlight:: html+jinja |
||||
|
|
||||
|
If you have used a different template engine in the past and want to switch |
||||
|
to Jinja2 here is a small guide that shows the basic syntactic and semantic |
||||
|
changes between some common, similar text template engines for Python. |
||||
|
|
||||
|
Jinja1 |
||||
|
------ |
||||
|
|
||||
|
Jinja2 is mostly compatible with Jinja1 in terms of API usage and template |
||||
|
syntax. The differences between Jinja1 and 2 are explained in the following |
||||
|
list. |
||||
|
|
||||
|
API |
||||
|
~~~ |
||||
|
|
||||
|
Loaders |
||||
|
Jinja2 uses a different loader API. Because the internal representation |
||||
|
of templates changed there is no longer support for external caching |
||||
|
systems such as memcached. The memory consumed by templates is comparable |
||||
|
with regular Python modules now and external caching doesn't give any |
||||
|
advantage. If you have used a custom loader in the past have a look at |
||||
|
the new :ref:`loader API <loaders>`. |
||||
|
|
||||
|
Loading templates from strings |
||||
|
In the past it was possible to generate templates from a string with the |
||||
|
default environment configuration by using `jinja.from_string`. Jinja2 |
||||
|
provides a :class:`Template` class that can be used to do the same, but |
||||
|
with optional additional configuration. |
||||
|
|
||||
|
Automatic unicode conversion |
||||
|
Jinja1 performed automatic conversion of bytestrings in a given encoding |
||||
|
into unicode objects. This conversion is no longer implemented as it |
||||
|
was inconsistent as most libraries are using the regular Python ASCII |
||||
|
bytestring to Unicode conversion. An application powered by Jinja2 |
||||
|
*has to* use unicode internally everywhere or make sure that Jinja2 only |
||||
|
gets unicode strings passed. |
||||
|
|
||||
|
i18n |
||||
|
Jinja1 used custom translators for internationalization. i18n is now |
||||
|
available as Jinja2 extension and uses a simpler, more gettext friendly |
||||
|
interface and has support for babel. For more details see |
||||
|
:ref:`i18n-extension`. |
||||
|
|
||||
|
Internal methods |
||||
|
Jinja1 exposed a few internal methods on the environment object such |
||||
|
as `call_function`, `get_attribute` and others. While they were marked |
||||
|
as being an internal method it was possible to override them. Jinja2 |
||||
|
doesn't have equivalent methods. |
||||
|
|
||||
|
Sandbox |
||||
|
Jinja1 was running sandbox mode by default. Few applications actually |
||||
|
used that feature so it became optional in Jinja2. For more details |
||||
|
about the sandboxed execution see :class:`SandboxedEnvironment`. |
||||
|
|
||||
|
Context |
||||
|
Jinja1 had a stacked context as storage for variables passed to the |
||||
|
environment. In Jinja2 a similar object exists but it doesn't allow |
||||
|
modifications nor is it a singleton. As inheritance is dynamic now |
||||
|
multiple context objects may exist during template evaluation. |
||||
|
|
||||
|
Filters and Tests |
||||
|
Filters and tests are regular functions now. It's no longer necessary |
||||
|
and allowed to use factory functions. |
||||
|
|
||||
|
|
||||
|
Templates |
||||
|
~~~~~~~~~ |
||||
|
|
||||
|
Jinja2 has mostly the same syntax as Jinja1. What's different is that |
||||
|
macros require parentheses around the argument list now. |
||||
|
|
||||
|
Additionally Jinja2 allows dynamic inheritance now and dynamic includes. |
||||
|
The old helper function `rendertemplate` is gone now, `include` can be used |
||||
|
instead. Includes no longer import macros and variable assignments, for |
||||
|
that the new `import` tag is used. This concept is explained in the |
||||
|
:ref:`import` documentation. |
||||
|
|
||||
|
Another small change happened in the `for`-tag. The special loop variable |
||||
|
doesn't have a `parent` attribute, instead you have to alias the loop |
||||
|
yourself. See :ref:`accessing-the-parent-loop` for more details. |
||||
|
|
||||
|
|
||||
|
Django |
||||
|
------ |
||||
|
|
||||
|
If you have previously worked with Django templates, you should find |
||||
|
Jinja2 very familiar. In fact, most of the syntax elements look and |
||||
|
work the same. |
||||
|
|
||||
|
However, Jinja2 provides some more syntax elements covered in the |
||||
|
documentation and some work a bit different. |
||||
|
|
||||
|
This section covers the template changes. As the API is fundamentally |
||||
|
different we won't cover it here. |
||||
|
|
||||
|
Method Calls |
||||
|
~~~~~~~~~~~~ |
||||
|
|
||||
|
In Django method calls work implicitly, while Jinja requires the explicit |
||||
|
Python syntax. Thus this Django code:: |
||||
|
|
||||
|
{% for page in user.get_created_pages %} |
||||
|
... |
||||
|
{% endfor %} |
||||
|
|
||||
|
...looks like this in Jinja:: |
||||
|
|
||||
|
{% for page in user.get_created_pages() %} |
||||
|
... |
||||
|
{% endfor %} |
||||
|
|
||||
|
This allows you to pass variables to the method, which is not possible in |
||||
|
Django. This syntax is also used for macros. |
||||
|
|
||||
|
Filter Arguments |
||||
|
~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Jinja2 provides more than one argument for filters. Also the syntax for |
||||
|
argument passing is different. A template that looks like this in Django:: |
||||
|
|
||||
|
{{ items|join:", " }} |
||||
|
|
||||
|
looks like this in Jinja2:: |
||||
|
|
||||
|
{{ items|join(', ') }} |
||||
|
|
||||
|
It is a bit more verbose, but it allows different types of arguments - |
||||
|
including variables - and more than one of them. |
||||
|
|
||||
|
Tests |
||||
|
~~~~~ |
||||
|
|
||||
|
In addition to filters there also are tests you can perform using the is |
||||
|
operator. Here are some examples:: |
||||
|
|
||||
|
{% if user.user_id is odd %} |
||||
|
{{ user.username|e }} is odd |
||||
|
{% else %} |
||||
|
hmm. {{ user.username|e }} looks pretty normal |
||||
|
{% endif %} |
||||
|
|
||||
|
Loops |
||||
|
~~~~~ |
||||
|
|
||||
|
For loops work very similarly to Django, but notably the Jinja2 special |
||||
|
variable for the loop context is called `loop`, not `forloop` as in Django. |
||||
|
|
||||
|
In addition, the Django `empty` argument is called `else` in Jinja2. For |
||||
|
example, the Django template:: |
||||
|
|
||||
|
{% for item in items %} |
||||
|
{{ item }} |
||||
|
{% empty %} |
||||
|
No items! |
||||
|
{% endfor %} |
||||
|
|
||||
|
...looks like this in Jinja2:: |
||||
|
|
||||
|
{% for item in items %} |
||||
|
{{ item }} |
||||
|
{% else %} |
||||
|
No items! |
||||
|
{% endfor %} |
||||
|
|
||||
|
Cycle |
||||
|
~~~~~ |
||||
|
|
||||
|
The ``{% cycle %}`` tag does not exist in Jinja2; however, you can achieve the |
||||
|
same output by using the `cycle` method on the loop context special variable. |
||||
|
|
||||
|
The following Django template:: |
||||
|
|
||||
|
{% for user in users %} |
||||
|
<li class="{% cycle 'odd' 'even' %}">{{ user }}</li> |
||||
|
{% endfor %} |
||||
|
|
||||
|
...looks like this in Jinja2:: |
||||
|
|
||||
|
{% for user in users %} |
||||
|
<li class="{{ loop.cycle('odd', 'even') }}">{{ user }}</li> |
||||
|
{% endfor %} |
||||
|
|
||||
|
There is no equivalent of ``{% cycle ... as variable %}``. |
||||
|
|
||||
|
|
||||
|
Mako |
||||
|
---- |
||||
|
|
||||
|
.. highlight:: html+mako |
||||
|
|
||||
|
If you have used Mako so far and want to switch to Jinja2 you can configure |
||||
|
Jinja2 to look more like Mako: |
||||
|
|
||||
|
.. sourcecode:: python |
||||
|
|
||||
|
env = Environment('<%', '%>', '${', '}', '<%doc>', '</%doc>', '%', '##') |
||||
|
|
||||
|
With an environment configured like that, Jinja2 should be able to interpret |
||||
|
a small subset of Mako templates. Jinja2 does not support embedded Python |
||||
|
code, so you would have to move that out of the template. The syntax for defs |
||||
|
(which are called macros in Jinja2) and template inheritance is different too. |
||||
|
The following Mako template:: |
||||
|
|
||||
|
<%inherit file="layout.html" /> |
||||
|
<%def name="title()">Page Title</%def> |
||||
|
<ul> |
||||
|
% for item in list: |
||||
|
<li>${item}</li> |
||||
|
% endfor |
||||
|
</ul> |
||||
|
|
||||
|
Looks like this in Jinja2 with the above configuration:: |
||||
|
|
||||
|
<% extends "layout.html" %> |
||||
|
<% block title %>Page Title<% endblock %> |
||||
|
<% block body %> |
||||
|
<ul> |
||||
|
% for item in list: |
||||
|
<li>${item}</li> |
||||
|
% endfor |
||||
|
</ul> |
||||
|
<% endblock %> |
File diff suppressed because it is too large
@ -0,0 +1,100 @@ |
|||||
|
Tips and Tricks |
||||
|
=============== |
||||
|
|
||||
|
.. highlight:: html+jinja |
||||
|
|
||||
|
This part of the documentation shows some tips and tricks for Jinja2 |
||||
|
templates. |
||||
|
|
||||
|
|
||||
|
.. _null-master-fallback: |
||||
|
|
||||
|
Null-Master Fallback |
||||
|
-------------------- |
||||
|
|
||||
|
Jinja2 supports dynamic inheritance and does not distinguish between parent |
||||
|
and child template as long as no `extends` tag is visited. While this leads |
||||
|
to the surprising behavior that everything before the first `extends` tag |
||||
|
including whitespace is printed out instead of being ignored, it can be used |
||||
|
for a neat trick. |
||||
|
|
||||
|
Usually child templates extend from one template that adds a basic HTML |
||||
|
skeleton. However it's possible to put the `extends` tag into an `if` tag to |
||||
|
only extend from the layout template if the `standalone` variable evaluates |
||||
|
to false which it does per default if it's not defined. Additionally a very |
||||
|
basic skeleton is added to the file so that if it's indeed rendered with |
||||
|
`standalone` set to `True` a very basic HTML skeleton is added:: |
||||
|
|
||||
|
{% if not standalone %}{% extends 'master.html' %}{% endif -%} |
||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
||||
|
<title>{% block title %}The Page Title{% endblock %}</title> |
||||
|
<link rel="stylesheet" href="style.css" type="text/css"> |
||||
|
{% block body %} |
||||
|
<p>This is the page body.</p> |
||||
|
{% endblock %} |
||||
|
|
||||
|
|
||||
|
Alternating Rows |
||||
|
---------------- |
||||
|
|
||||
|
If you want to have different styles for each row of a table or |
||||
|
list you can use the `cycle` method on the `loop` object:: |
||||
|
|
||||
|
<ul> |
||||
|
{% for row in rows %} |
||||
|
<li class="{{ loop.cycle('odd', 'even') }}">{{ row }}</li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
|
||||
|
`cycle` can take an unlimited amount of strings. Each time this |
||||
|
tag is encountered the next item from the list is rendered. |
||||
|
|
||||
|
|
||||
|
Highlighting Active Menu Items |
||||
|
------------------------------ |
||||
|
|
||||
|
Often you want to have a navigation bar with an active navigation |
||||
|
item. This is really simple to achieve. Because assignments outside |
||||
|
of `block`\s in child templates are global and executed before the layout |
||||
|
template is evaluated it's possible to define the active menu item in the |
||||
|
child template:: |
||||
|
|
||||
|
{% extends "layout.html" %} |
||||
|
{% set active_page = "index" %} |
||||
|
|
||||
|
The layout template can then access `active_page`. Additionally it makes |
||||
|
sense to define a default for that variable:: |
||||
|
|
||||
|
{% set navigation_bar = [ |
||||
|
('/', 'index', 'Index'), |
||||
|
('/downloads/', 'downloads', 'Downloads'), |
||||
|
('/about/', 'about', 'About') |
||||
|
] -%} |
||||
|
{% set active_page = active_page|default('index') -%} |
||||
|
... |
||||
|
<ul id="navigation"> |
||||
|
{% for href, id, caption in navigation_bar %} |
||||
|
<li{% if id == active_page %} class="active"{% endif |
||||
|
%}><a href="{{ href|e }}">{{ caption|e }}</a></li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
... |
||||
|
|
||||
|
.. _accessing-the-parent-loop: |
||||
|
|
||||
|
Accessing the parent Loop |
||||
|
------------------------- |
||||
|
|
||||
|
The special `loop` variable always points to the innermost loop. If it's |
||||
|
desired to have access to an outer loop it's possible to alias it:: |
||||
|
|
||||
|
<table> |
||||
|
{% for row in table %} |
||||
|
<tr> |
||||
|
{% set rowloop = loop %} |
||||
|
{% for cell in row %} |
||||
|
<td id="cell-{{ rowloop.index }}-{{ loop.index }}">{{ cell }}</td> |
||||
|
{% endfor %} |
||||
|
</tr> |
||||
|
{% endfor %} |
||||
|
</table> |
@ -0,0 +1,13 @@ |
|||||
|
from jinja2 import Environment |
||||
|
|
||||
|
|
||||
|
env = Environment(line_statement_prefix="#", variable_start_string="${", variable_end_string="}") |
||||
|
|
||||
|
|
||||
|
print env.from_string("""\ |
||||
|
<ul> |
||||
|
# for item in range(10) |
||||
|
<li class="${loop.cycle('odd', 'even')}">${item}</li> |
||||
|
# endfor |
||||
|
</ul>\ |
||||
|
""").render() |
@ -0,0 +1,7 @@ |
|||||
|
from jinja2 import Environment |
||||
|
from jinja2.loaders import FileSystemLoader |
||||
|
|
||||
|
env = Environment(loader=FileSystemLoader('templates')) |
||||
|
|
||||
|
tmpl = env.get_template('broken.html') |
||||
|
print tmpl.render(seq=[3, 2, 4, 5, 3, 2, 0, 2, 1]) |
@ -0,0 +1,12 @@ |
|||||
|
from jinja2 import Environment |
||||
|
from jinja2.loaders import DictLoader |
||||
|
|
||||
|
|
||||
|
env = Environment(loader=DictLoader({ |
||||
|
'a': '''[A[{% block body %}{% endblock %}]]''', |
||||
|
'b': '''{% extends 'a' %}{% block body %}[B]{% endblock %}''', |
||||
|
'c': '''{% extends 'b' %}{% block body %}###{{ super() }}###{% endblock %}''' |
||||
|
})) |
||||
|
|
||||
|
|
||||
|
print env.get_template('c').render() |
@ -0,0 +1,6 @@ |
|||||
|
{% from 'subbroken.html' import may_break %} |
||||
|
<ul> |
||||
|
{% for item in seq %} |
||||
|
<li>{{ may_break(item) }}</li> |
||||
|
{% endfor %} |
||||
|
</ul> |
@ -0,0 +1,3 @@ |
|||||
|
{% macro may_break(item) -%} |
||||
|
[{{ item / 0 }}] |
||||
|
{%- endmacro %} |
@ -0,0 +1,27 @@ |
|||||
|
from jinja2 import Environment |
||||
|
from jinja2.loaders import DictLoader |
||||
|
|
||||
|
env = Environment(loader=DictLoader({ |
||||
|
'child.html': u'''\ |
||||
|
{% extends master_layout or 'master.html' %} |
||||
|
{% include helpers = 'helpers.html' %} |
||||
|
{% macro get_the_answer() %}42{% endmacro %} |
||||
|
{% title = 'Hello World' %} |
||||
|
{% block body %} |
||||
|
{{ get_the_answer() }} |
||||
|
{{ helpers.conspirate() }} |
||||
|
{% endblock %} |
||||
|
''', |
||||
|
'master.html': u'''\ |
||||
|
<!doctype html> |
||||
|
<title>{{ title }}</title> |
||||
|
{% block body %}{% endblock %} |
||||
|
''', |
||||
|
'helpers.html': u'''\ |
||||
|
{% macro conspirate() %}23{% endmacro %} |
||||
|
''' |
||||
|
})) |
||||
|
|
||||
|
|
||||
|
tmpl = env.get_template("child.html") |
||||
|
print(tmpl.render()) |
@ -0,0 +1,25 @@ |
|||||
|
from jinja2 import Environment |
||||
|
|
||||
|
|
||||
|
env = Environment(line_statement_prefix='%', variable_start_string="${", variable_end_string="}") |
||||
|
tmpl = env.from_string("""\ |
||||
|
% macro foo() |
||||
|
${caller(42)} |
||||
|
% endmacro |
||||
|
<ul> |
||||
|
% for item in seq |
||||
|
<li>${item}</li> |
||||
|
% endfor |
||||
|
</ul> |
||||
|
% call(var) foo() |
||||
|
[${var}] |
||||
|
% endcall |
||||
|
% filter escape |
||||
|
<hello world> |
||||
|
% for item in [1, 2, 3] |
||||
|
- ${item} |
||||
|
% endfor |
||||
|
% endfilter |
||||
|
""") |
||||
|
|
||||
|
print(tmpl.render(seq=range(10))) |
@ -0,0 +1,12 @@ |
|||||
|
from jinja2 import Environment |
||||
|
|
||||
|
tmpl = Environment().from_string("""\ |
||||
|
<ul> |
||||
|
{%- for item in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] if item % 2 == 0 %} |
||||
|
<li>{{ loop.index }} / {{ loop.length }}: {{ item }}</li> |
||||
|
{%- endfor %} |
||||
|
</ul> |
||||
|
if condition: {{ 1 if foo else 0 }} |
||||
|
""") |
||||
|
|
||||
|
print(tmpl.render(foo=True)) |
@ -0,0 +1,14 @@ |
|||||
|
from jinja2 import Environment |
||||
|
|
||||
|
env = Environment(extensions=['jinja2.ext.i18n']) |
||||
|
env.globals['gettext'] = { |
||||
|
'Hello %(user)s!': 'Hallo %(user)s!' |
||||
|
}.__getitem__ |
||||
|
env.globals['ngettext'] = lambda s, p, n: { |
||||
|
'%(count)s user': '%(count)d Benutzer', |
||||
|
'%(count)s users': '%(count)d Benutzer' |
||||
|
}[n == 1 and s or p] |
||||
|
print env.from_string("""\ |
||||
|
{% trans %}Hello {{ user }}!{% endtrans %} |
||||
|
{% trans count=users|count %}{{ count }} user{% pluralize %}{{ count }} users{% endtrans %} |
||||
|
""").render(user="someone", users=[1, 2, 3]) |
@ -0,0 +1,433 @@ |
|||||
|
"""\ |
||||
|
This benchmark compares some python templating engines with Jinja 2 so |
||||
|
that we get a picture of how fast Jinja 2 is for a semi real world |
||||
|
template. If a template engine is not installed the test is skipped.\ |
||||
|
""" |
||||
|
import sys |
||||
|
import cgi |
||||
|
from timeit import Timer |
||||
|
from jinja2 import Environment as JinjaEnvironment |
||||
|
|
||||
|
context = { |
||||
|
'page_title': 'mitsuhiko\'s benchmark', |
||||
|
'table': [dict(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9,j=10) for x in range(1000)] |
||||
|
} |
||||
|
|
||||
|
jinja_template = JinjaEnvironment( |
||||
|
line_statement_prefix='%', |
||||
|
variable_start_string="${", |
||||
|
variable_end_string="}" |
||||
|
).from_string("""\ |
||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>${page_title|e}</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>${page_title|e}</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
% for href, caption in [ |
||||
|
('index.html', 'Index'), |
||||
|
('downloads.html', 'Downloads'), |
||||
|
('products.html', 'Products') |
||||
|
] |
||||
|
<li><a href="${href|e}">${caption|e}</a></li> |
||||
|
% endfor |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
% for row in table |
||||
|
<tr> |
||||
|
% for cell in row |
||||
|
<td>${cell}</td> |
||||
|
% endfor |
||||
|
</tr> |
||||
|
% endfor |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""") |
||||
|
|
||||
|
def test_jinja(): |
||||
|
jinja_template.render(context) |
||||
|
|
||||
|
try: |
||||
|
from tornado.template import Template |
||||
|
except ImportError: |
||||
|
test_tornado = None |
||||
|
else: |
||||
|
tornado_template = Template("""\ |
||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>{{ page_title }}</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>{{ page_title }}</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
{% for href, caption in [ \ |
||||
|
('index.html', 'Index'), \ |
||||
|
('downloads.html', 'Downloads'), \ |
||||
|
('products.html', 'Products') \ |
||||
|
] %} |
||||
|
<li><a href="{{ href }}">{{ caption }}</a></li> |
||||
|
{% end %} |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
{% for row in table %} |
||||
|
<tr> |
||||
|
{% for cell in row %} |
||||
|
<td>{{ cell }}</td> |
||||
|
{% end %} |
||||
|
</tr> |
||||
|
{% end %} |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""") |
||||
|
|
||||
|
def test_tornado(): |
||||
|
tornado_template.generate(**context) |
||||
|
|
||||
|
try: |
||||
|
from django.conf import settings |
||||
|
settings.configure() |
||||
|
from django.template import Template as DjangoTemplate, Context as DjangoContext |
||||
|
except ImportError: |
||||
|
test_django = None |
||||
|
else: |
||||
|
django_template = DjangoTemplate("""\ |
||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>{{ page_title }}</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>{{ page_title }}</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
{% for href, caption in navigation %} |
||||
|
<li><a href="{{ href }}">{{ caption }}</a></li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
{% for row in table %} |
||||
|
<tr> |
||||
|
{% for cell in row %} |
||||
|
<td>{{ cell }}</td> |
||||
|
{% endfor %} |
||||
|
</tr> |
||||
|
{% endfor %} |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""") |
||||
|
|
||||
|
def test_django(): |
||||
|
c = DjangoContext(context) |
||||
|
c['navigation'] = [('index.html', 'Index'), ('downloads.html', 'Downloads'), |
||||
|
('products.html', 'Products')] |
||||
|
django_template.render(c) |
||||
|
|
||||
|
try: |
||||
|
from mako.template import Template as MakoTemplate |
||||
|
except ImportError: |
||||
|
test_mako = None |
||||
|
else: |
||||
|
mako_template = MakoTemplate("""\ |
||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>${page_title|h}</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>${page_title|h}</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
% for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: |
||||
|
<li><a href="${href|h}">${caption|h}</a></li> |
||||
|
% endfor |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
% for row in table: |
||||
|
<tr> |
||||
|
% for cell in row: |
||||
|
<td>${cell}</td> |
||||
|
% endfor |
||||
|
</tr> |
||||
|
% endfor |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""") |
||||
|
|
||||
|
def test_mako(): |
||||
|
mako_template.render(**context) |
||||
|
|
||||
|
try: |
||||
|
from genshi.template import MarkupTemplate as GenshiTemplate |
||||
|
except ImportError: |
||||
|
test_genshi = None |
||||
|
else: |
||||
|
genshi_template = GenshiTemplate("""\ |
||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/"> |
||||
|
<head> |
||||
|
<title>${page_title}</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>${page_title}</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
<li py:for="href, caption in [ |
||||
|
('index.html', 'Index'), |
||||
|
('downloads.html', 'Downloads'), |
||||
|
('products.html', 'Products')]"><a href="${href}">${caption}</a></li> |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
<tr py:for="row in table"> |
||||
|
<td py:for="cell in row">${cell}</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""") |
||||
|
|
||||
|
def test_genshi(): |
||||
|
genshi_template.generate(**context).render('html', strip_whitespace=False) |
||||
|
|
||||
|
try: |
||||
|
from Cheetah.Template import Template as CheetahTemplate |
||||
|
except ImportError: |
||||
|
test_cheetah = None |
||||
|
else: |
||||
|
cheetah_template = CheetahTemplate("""\ |
||||
|
#import cgi |
||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>$cgi.escape($page_title)</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>$cgi.escape($page_title)</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
#for $href, $caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: |
||||
|
<li><a href="$cgi.escape($href)">$cgi.escape($caption)</a></li> |
||||
|
#end for |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
#for $row in $table: |
||||
|
<tr> |
||||
|
#for $cell in $row: |
||||
|
<td>$cell</td> |
||||
|
#end for |
||||
|
</tr> |
||||
|
#end for |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""", searchList=[dict(context)]) |
||||
|
|
||||
|
def test_cheetah(): |
||||
|
unicode(cheetah_template) |
||||
|
|
||||
|
try: |
||||
|
import tenjin |
||||
|
except ImportError: |
||||
|
test_tenjin = None |
||||
|
else: |
||||
|
tenjin_template = tenjin.Template() |
||||
|
tenjin_template.convert("""\ |
||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>${page_title}</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>${page_title}</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
<?py for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: ?> |
||||
|
<li><a href="${href}">${caption}</a></li> |
||||
|
<?py #end ?> |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
<?py for row in table: ?> |
||||
|
<tr> |
||||
|
<?py for cell in row: ?> |
||||
|
<td>#{cell}</td> |
||||
|
<?py #end ?> |
||||
|
</tr> |
||||
|
<?py #end ?> |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""") |
||||
|
|
||||
|
def test_tenjin(): |
||||
|
from tenjin.helpers import escape, to_str |
||||
|
tenjin_template.render(context, locals()) |
||||
|
|
||||
|
try: |
||||
|
from spitfire.compiler import util as SpitfireTemplate |
||||
|
from spitfire.compiler.analyzer import o2_options as spitfire_optimizer |
||||
|
except ImportError: |
||||
|
test_spitfire = None |
||||
|
else: |
||||
|
spitfire_template = SpitfireTemplate.load_template("""\ |
||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>$cgi.escape($page_title)</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>$cgi.escape($page_title)</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
#for $href, $caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')] |
||||
|
<li><a href="$cgi.escape($href)">$cgi.escape($caption)</a></li> |
||||
|
#end for |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
#for $row in $table |
||||
|
<tr> |
||||
|
#for $cell in $row |
||||
|
<td>$cell</td> |
||||
|
#end for |
||||
|
</tr> |
||||
|
#end for |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""", 'spitfire_tmpl', spitfire_optimizer, {'enable_filters': False}) |
||||
|
spitfire_context = dict(context, **{'cgi': cgi}) |
||||
|
|
||||
|
def test_spitfire(): |
||||
|
spitfire_template(search_list=[spitfire_context]).main() |
||||
|
|
||||
|
|
||||
|
try: |
||||
|
from chameleon.zpt.template import PageTemplate |
||||
|
except ImportError: |
||||
|
test_chameleon = None |
||||
|
else: |
||||
|
chameleon_template = PageTemplate("""\ |
||||
|
<html xmlns:tal="http://xml.zope.org/namespaces/tal"> |
||||
|
<head> |
||||
|
<title tal:content="page_title">Page Title</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1 tal:content="page_title">Page Title</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
<li tal:repeat="item sections"><a tal:attributes="href item[0]" tal:content="item[1]">caption</a></li> |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
<tr tal:repeat="row table"> |
||||
|
<td tal:repeat="cell row" tal:content="row[cell]">cell</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""") |
||||
|
chameleon_context = dict(context) |
||||
|
chameleon_context['sections'] = [ |
||||
|
('index.html', 'Index'), |
||||
|
('downloads.html', 'Downloads'), |
||||
|
('products.html', 'Products') |
||||
|
] |
||||
|
def test_chameleon(): |
||||
|
chameleon_template.render(**chameleon_context) |
||||
|
|
||||
|
try: |
||||
|
from chameleon.zpt.template import PageTemplate |
||||
|
from chameleon.genshi import language |
||||
|
except ImportError: |
||||
|
test_chameleon_genshi = None |
||||
|
else: |
||||
|
chameleon_genshi_template = PageTemplate("""\ |
||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/"> |
||||
|
<head> |
||||
|
<title>${page_title}</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>${page_title}</h1> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
<li py:for="info in sections"><a href="${info[0]}">${info[1]}</a></li> |
||||
|
</ul> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
<tr py:for="row in table"> |
||||
|
<td py:for="cell in row">${row[cell]}</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""", parser=language.Parser()) |
||||
|
chameleon_genshi_context = dict(context) |
||||
|
chameleon_genshi_context['sections'] = [ |
||||
|
('index.html', 'Index'), |
||||
|
('downloads.html', 'Downloads'), |
||||
|
('products.html', 'Products') |
||||
|
] |
||||
|
def test_chameleon_genshi(): |
||||
|
chameleon_genshi_template.render(**chameleon_genshi_context) |
||||
|
|
||||
|
|
||||
|
sys.stdout.write('\r' + '\n'.join(( |
||||
|
'=' * 80, |
||||
|
'Template Engine BigTable Benchmark'.center(80), |
||||
|
'=' * 80, |
||||
|
__doc__, |
||||
|
'-' * 80 |
||||
|
)) + '\n') |
||||
|
|
||||
|
|
||||
|
for test in 'jinja', 'mako', 'tornado', 'tenjin', 'spitfire', 'django', 'genshi', 'cheetah', 'chameleon', 'chameleon_genshi': |
||||
|
if locals()['test_' + test] is None: |
||||
|
sys.stdout.write(' %-20s*not installed*\n' % test) |
||||
|
continue |
||||
|
t = Timer(setup='from __main__ import test_%s as bench' % test, |
||||
|
stmt='bench()') |
||||
|
sys.stdout.write(' >> %-20s<running>' % test) |
||||
|
sys.stdout.flush() |
||||
|
sys.stdout.write('\r %-20s%.4f seconds\n' % (test, t.timeit(number=50) / 50)) |
||||
|
sys.stdout.write('-' * 80 + '\n') |
||||
|
sys.stdout.write('''\ |
||||
|
WARNING: The results of this benchmark are useless to compare the |
||||
|
performance of template engines and should not be taken seriously in any |
||||
|
way. It's testing the performance of simple loops and has no real-world |
||||
|
usefulnes. It only used to check if changes on the Jinja code affect |
||||
|
performance in a good or bad way and how it roughly compares to others. |
||||
|
''' + '=' * 80 + '\n') |
@ -0,0 +1,52 @@ |
|||||
|
try: |
||||
|
from cProfile import Profile |
||||
|
except ImportError: |
||||
|
from profile import Profile |
||||
|
from pstats import Stats |
||||
|
from jinja2 import Environment as JinjaEnvironment |
||||
|
|
||||
|
context = { |
||||
|
'page_title': 'mitsuhiko\'s benchmark', |
||||
|
'table': [dict(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9,j=10) for x in range(1000)] |
||||
|
} |
||||
|
|
||||
|
source = """\ |
||||
|
% macro testmacro(x) |
||||
|
<span>${x}</span> |
||||
|
% endmacro |
||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>${page_title|e}</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="header"> |
||||
|
<h1>${page_title|e}</h1> |
||||
|
</div> |
||||
|
<div class="table"> |
||||
|
<table> |
||||
|
% for row in table |
||||
|
<tr> |
||||
|
% for cell in row |
||||
|
<td>${testmacro(cell)}</td> |
||||
|
% endfor |
||||
|
</tr> |
||||
|
% endfor |
||||
|
</table> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html>\ |
||||
|
""" |
||||
|
jinja_template = JinjaEnvironment( |
||||
|
line_statement_prefix='%', |
||||
|
variable_start_string="${", |
||||
|
variable_end_string="}" |
||||
|
).from_string(source) |
||||
|
print jinja_template.environment.compile(source, raw=True) |
||||
|
|
||||
|
|
||||
|
p = Profile() |
||||
|
p.runcall(lambda: jinja_template.render(context)) |
||||
|
stats = Stats(p) |
||||
|
stats.sort_stats('time', 'calls') |
||||
|
stats.print_stats() |
@ -0,0 +1 @@ |
|||||
|
<form action="{{ action }}" method="{{ method }}">{{ body }}</form> |
@ -0,0 +1 @@ |
|||||
|
<input type="{{ type }}" value="{{ value }}" name="{{ name }}"> |
@ -0,0 +1 @@ |
|||||
|
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}">{{ value }}</textarea> |
@ -0,0 +1,29 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% block page_title %}Index Page{% endblock %} |
||||
|
{% block body %} |
||||
|
{% for article in articles %} |
||||
|
{% if article.published %} |
||||
|
<div class="article"> |
||||
|
<h2><a href="{{ article.href }}">{{ article.title }}</a></h2> |
||||
|
<p class="meta">written by <a href="{{ article.user.href }}">{{ article.user.username }}</a> on {{ article.pub_date|dateformat }}</p> |
||||
|
<div class="text">{{ article.body|safe }}</div> |
||||
|
</div> |
||||
|
{% endif %} |
||||
|
{% endfor %} |
||||
|
{% form %} |
||||
|
<dl> |
||||
|
<dt>Name</dt> |
||||
|
<dd>{% input_field 'name' %}</dd> |
||||
|
<dt>E-Mail</dt> |
||||
|
<dd>{% input_field 'email' %}</dd> |
||||
|
<dt>URL</dt> |
||||
|
<dd>{% input_field 'url' %}</dd> |
||||
|
<dt>Comment</dt> |
||||
|
<dd>{% textarea 'comment' %}</dd> |
||||
|
<dt>Captcha</dt> |
||||
|
<dd>{% input_field 'captcha' %}</dd> |
||||
|
</dl> |
||||
|
{% input_field '' 'submit' 'Submit' %} |
||||
|
{% input_field 'cancel' 'submit' 'Cancel' %} |
||||
|
{% endform %} |
||||
|
{% endblock %} |
@ -0,0 +1,29 @@ |
|||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>{% block page_title %}{% endblock %} | RealWorld Benchmark</title> |
||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="contents"> |
||||
|
<div class="header"> |
||||
|
<h1>RealWorld Benchmark</h1> |
||||
|
<blockquote><p> |
||||
|
A less stupid benchmark for Mako and Jinja2 to get an impression how |
||||
|
code changes affect runtime performance. |
||||
|
</p></blockquote> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
{% for href, caption in page_navigation %} |
||||
|
<li><a href="{{ href }}">{{ caption }}</a></li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
<div class="body"> |
||||
|
{% block body %}{% endblock %} |
||||
|
</div> |
||||
|
<div class="footer"> |
||||
|
© Copyright 2008 by I don't know who. |
||||
|
</div> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,135 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
from rwbench import ROOT |
||||
|
from os.path import join |
||||
|
from django.conf import settings |
||||
|
settings.configure( |
||||
|
TEMPLATE_DIRS=(join(ROOT, 'django'),), |
||||
|
TEMPLATE_LOADERS=( |
||||
|
('django.template.loaders.cached.Loader', ( |
||||
|
'django.template.loaders.filesystem.Loader', |
||||
|
)), |
||||
|
) |
||||
|
) |
||||
|
from django.template import loader as django_loader, Context as DjangoContext, \ |
||||
|
Node, NodeList, Variable, TokenParser |
||||
|
from django import template as django_template_module |
||||
|
from django.template import Library |
||||
|
|
||||
|
|
||||
|
# for django extensions. We monkey patch our extensions in so that |
||||
|
# we don't have to initialize a more complex django setup. |
||||
|
django_extensions = django_template_module.Library() |
||||
|
django_template_module.builtins.append(django_extensions) |
||||
|
|
||||
|
|
||||
|
from rwbench import dateformat |
||||
|
django_extensions.filter(dateformat) |
||||
|
|
||||
|
|
||||
|
def var_or_none(x): |
||||
|
if x is not None: |
||||
|
return Variable(x) |
||||
|
|
||||
|
|
||||
|
# and more django extensions |
||||
|
@django_extensions.tag |
||||
|
def input_field(parser, token): |
||||
|
p = TokenParser(token.contents) |
||||
|
args = [p.value()] |
||||
|
while p.more(): |
||||
|
args.append(p.value()) |
||||
|
return InputFieldNode(*args) |
||||
|
|
||||
|
|
||||
|
@django_extensions.tag |
||||
|
def textarea(parser, token): |
||||
|
p = TokenParser(token.contents) |
||||
|
args = [p.value()] |
||||
|
while p.more(): |
||||
|
args.append(p.value()) |
||||
|
return TextareaNode(*args) |
||||
|
|
||||
|
|
||||
|
@django_extensions.tag |
||||
|
def form(parser, token): |
||||
|
p = TokenParser(token.contents) |
||||
|
args = [] |
||||
|
while p.more(): |
||||
|
args.append(p.value()) |
||||
|
body = parser.parse(('endform',)) |
||||
|
parser.delete_first_token() |
||||
|
return FormNode(body, *args) |
||||
|
|
||||
|
|
||||
|
class InputFieldNode(Node): |
||||
|
|
||||
|
def __init__(self, name, type=None, value=None): |
||||
|
self.name = var_or_none(name) |
||||
|
self.type = var_or_none(type) |
||||
|
self.value = var_or_none(value) |
||||
|
|
||||
|
def render(self, context): |
||||
|
name = self.name.resolve(context) |
||||
|
type = 'text' |
||||
|
value = '' |
||||
|
if self.type is not None: |
||||
|
type = self.type.resolve(context) |
||||
|
if self.value is not None: |
||||
|
value = self.value.resolve(context) |
||||
|
tmpl = django_loader.get_template('_input_field.html') |
||||
|
return tmpl.render(DjangoContext({ |
||||
|
'name': name, |
||||
|
'type': type, |
||||
|
'value': value |
||||
|
})) |
||||
|
|
||||
|
|
||||
|
class TextareaNode(Node): |
||||
|
|
||||
|
def __init__(self, name, rows=None, cols=None, value=None): |
||||
|
self.name = var_or_none(name) |
||||
|
self.rows = var_or_none(rows) |
||||
|
self.cols = var_or_none(cols) |
||||
|
self.value = var_or_none(value) |
||||
|
|
||||
|
def render(self, context): |
||||
|
name = self.name.resolve(context) |
||||
|
rows = 10 |
||||
|
cols = 40 |
||||
|
value = '' |
||||
|
if self.rows is not None: |
||||
|
rows = int(self.rows.resolve(context)) |
||||
|
if self.cols is not None: |
||||
|
cols = int(self.cols.resolve(context)) |
||||
|
if self.value is not None: |
||||
|
value = self.value.resolve(context) |
||||
|
tmpl = django_loader.get_template('_textarea.html') |
||||
|
return tmpl.render(DjangoContext({ |
||||
|
'name': name, |
||||
|
'rows': rows, |
||||
|
'cols': cols, |
||||
|
'value': value |
||||
|
})) |
||||
|
|
||||
|
|
||||
|
class FormNode(Node): |
||||
|
|
||||
|
def __init__(self, body, action=None, method=None): |
||||
|
self.body = body |
||||
|
self.action = action |
||||
|
self.method = method |
||||
|
|
||||
|
def render(self, context): |
||||
|
body = self.body.render(context) |
||||
|
action = '' |
||||
|
method = 'post' |
||||
|
if self.action is not None: |
||||
|
action = self.action.resolve(context) |
||||
|
if self.method is not None: |
||||
|
method = self.method.resolve(context) |
||||
|
tmpl = django_loader.get_template('_form.html') |
||||
|
return tmpl.render(DjangoContext({ |
||||
|
'body': body, |
||||
|
'action': action, |
||||
|
'method': method |
||||
|
})) |
@ -0,0 +1,12 @@ |
|||||
|
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" |
||||
|
py:strip=""> |
||||
|
|
||||
|
<py:def function="input_field(name='', value='', type='text')"> |
||||
|
<input type="$type" value="$value" name="$name" /> |
||||
|
</py:def> |
||||
|
|
||||
|
<py:def function="textarea(name, value='', rows=10, cols=40)"> |
||||
|
<textarea name="$name" rows="$rows" cols="cols">$value</textarea> |
||||
|
</py:def> |
||||
|
|
||||
|
</div> |
@ -0,0 +1,41 @@ |
|||||
|
<?python |
||||
|
from rwbench import dateformat |
||||
|
?> |
||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude" |
||||
|
xmlns:py="http://genshi.edgewall.org/"> |
||||
|
<xi:include href="layout.html" /> |
||||
|
<xi:include href="helpers.html" /> |
||||
|
<head><title>Index Page</title></head> |
||||
|
<body> |
||||
|
<div class="article" py:for="article in articles"> |
||||
|
<py:if test="article.published"> |
||||
|
<h2><a href="${article.href}">${article.title}</a></h2> |
||||
|
<p class="meta">written by <a href="${article.user.href}" |
||||
|
>${article.user.username}</a> on ${dateformat(article.pub_date)}</p> |
||||
|
<div class="text">${Markup(article.body)}</div> |
||||
|
</py:if> |
||||
|
</div> |
||||
|
<!-- |
||||
|
For a fair and balanced comparison we would have to use a def here |
||||
|
that wraps the form data but I don't know what would be the best |
||||
|
Genshi equivalent for that. Quite frankly I doubt that this makes |
||||
|
sense in Genshi anyways. |
||||
|
--> |
||||
|
<form action="" method="post"> |
||||
|
<dl> |
||||
|
<dt>Name</dt> |
||||
|
<dd>${input_field('name')}</dd> |
||||
|
<dt>E-Mail</dt> |
||||
|
<dd>${input_field('email')}</dd> |
||||
|
<dt>URL</dt> |
||||
|
<dd>${input_field('url')}</dd> |
||||
|
<dt>Comment</dt> |
||||
|
<dd>${textarea('comment')}</dd> |
||||
|
<dt>Captcha</dt> |
||||
|
<dd>${input_field('captcha')}</dd> |
||||
|
</dl> |
||||
|
${input_field(type='submit', value='Submit')} |
||||
|
${input_field(name='cancel', type='submit', value='Cancel')} |
||||
|
</form> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,30 @@ |
|||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" > |
||||
|
<py:match path="head" once="true"> |
||||
|
<head> |
||||
|
<title>${select('title/text()')} | RealWorld Benchmark</title> |
||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
||||
|
</head> |
||||
|
</py:match> |
||||
|
<py:match path="body" once="true"> |
||||
|
<body> |
||||
|
<div class="contents"> |
||||
|
<div class="header"> |
||||
|
<h1>RealWorld Benchmark</h1> |
||||
|
<blockquote><p> |
||||
|
A less stupid benchmark for Mako and Jinja2 to get an impression how |
||||
|
code changes affect runtime performance. |
||||
|
</p></blockquote> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
<li py:for="href, caption in page_navigation"><a href="$href">$caption</a></li> |
||||
|
</ul> |
||||
|
<div class="body"> |
||||
|
${select('*|text()')} |
||||
|
</div> |
||||
|
<div class="footer"> |
||||
|
© Copyright 2008 by I don't know who. |
||||
|
</div> |
||||
|
</div> |
||||
|
</body> |
||||
|
</py:match> |
||||
|
</html> |
@ -0,0 +1,12 @@ |
|||||
|
{% macro input_field(name, value='', type='text') -%} |
||||
|
<input type="{{ type }}" value="{{ value|e }}" name="{{ name }}"> |
||||
|
{%- endmacro %} |
||||
|
|
||||
|
{% macro textarea(name, value='', rows=10, cols=40) -%} |
||||
|
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}">{{ |
||||
|
value|e }}</textarea> |
||||
|
{%- endmacro %} |
||||
|
|
||||
|
{% macro form(action='', method='post') -%} |
||||
|
<form action="{{ action|e }}" method="{{ method }}">{{ caller() }}</form> |
||||
|
{%- endmacro %} |
@ -0,0 +1,29 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% from "helpers.html" import input_field, textarea, form %} |
||||
|
{% block page_title %}Index Page{% endblock %} |
||||
|
{% block body %} |
||||
|
{%- for article in articles if article.published %} |
||||
|
<div class="article"> |
||||
|
<h2><a href="{{ article.href|e }}">{{ article.title|e }}</a></h2> |
||||
|
<p class="meta">written by <a href="{{ article.user.href|e |
||||
|
}}">{{ article.user.username|e }}</a> on {{ article.pub_date|dateformat }}</p> |
||||
|
<div class="text">{{ article.body }}</div> |
||||
|
</div> |
||||
|
{%- endfor %} |
||||
|
{%- call form() %} |
||||
|
<dl> |
||||
|
<dt>Name</dt> |
||||
|
<dd>{{ input_field('name') }}</dd> |
||||
|
<dt>E-Mail</dt> |
||||
|
<dd>{{ input_field('email') }}</dd> |
||||
|
<dt>URL</dt> |
||||
|
<dd>{{ input_field('url') }}</dd> |
||||
|
<dt>Comment</dt> |
||||
|
<dd>{{ textarea('comment') }}</dd> |
||||
|
<dt>Captcha</dt> |
||||
|
<dd>{{ input_field('captcha') }}</dd> |
||||
|
</dl> |
||||
|
{{ input_field(type='submit', value='Submit') }} |
||||
|
{{ input_field('cancel', type='submit', value='Cancel') }} |
||||
|
{%- endcall %} |
||||
|
{% endblock %} |
@ -0,0 +1,29 @@ |
|||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>{% block page_title %}{% endblock %} | RealWorld Benchmark</title> |
||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="contents"> |
||||
|
<div class="header"> |
||||
|
<h1>RealWorld Benchmark</h1> |
||||
|
<blockquote><p> |
||||
|
A less stupid benchmark for Mako and Jinja2 to get an impression how |
||||
|
code changes affect runtime performance. |
||||
|
</p></blockquote> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
{%- for href, caption in page_navigation %} |
||||
|
<li><a href="{{ href|e }}">{{ caption }}</a></li> |
||||
|
{%- endfor %} |
||||
|
</ul> |
||||
|
<div class="body"> |
||||
|
{% block body %}{% endblock %} |
||||
|
</div> |
||||
|
<div class="footer"> |
||||
|
© Copyright 2008 by I don't know who. |
||||
|
</div> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,11 @@ |
|||||
|
<%def name="input_field(name='', value='', type='text')"> |
||||
|
<input type="${type}" value="${value|h}" name="${name}"> |
||||
|
</%def> |
||||
|
|
||||
|
<%def name="textarea(name, value='', rows=10, cols=40)"> |
||||
|
<textarea name="${name}" rows="${rows}" cols="${cols}">${value|h}</textarea> |
||||
|
</%def> |
||||
|
|
||||
|
<%def name="form(action='', method='post')"> |
||||
|
<form action="${action|h}" method="${method}">${caller.body()}</form> |
||||
|
</%def> |
@ -0,0 +1,31 @@ |
|||||
|
<%! |
||||
|
from rwbench import dateformat |
||||
|
%> |
||||
|
<%inherit file="layout.html" /> |
||||
|
<%namespace file="helpers.html" import="input_field, textarea, form" /> |
||||
|
<%def name="page_title()">Index Page</%def> |
||||
|
% for article in articles: |
||||
|
<% if not article.published: continue %> |
||||
|
<div class="article"> |
||||
|
<h2><a href="${article.href|h}">${article.title|h}</a></h2> |
||||
|
<p class="meta">written by <a href="${article.user.href|h |
||||
|
}">${article.user.username|h}</a> on ${dateformat(article.pub_date)}</p> |
||||
|
<div class="text">${article.body}</div> |
||||
|
</div> |
||||
|
% endfor |
||||
|
<%call expr="form()"> |
||||
|
<dl> |
||||
|
<dt>Name</dt> |
||||
|
<dd>${input_field('name')}</dd> |
||||
|
<dt>E-Mail</dt> |
||||
|
<dd>${input_field('email')}</dd> |
||||
|
<dt>URL</dt> |
||||
|
<dd>${input_field('url')}</dd> |
||||
|
<dt>Comment</dt> |
||||
|
<dd>${textarea('comment')}</dd> |
||||
|
<dt>Captcha</dt> |
||||
|
<dd>${input_field('captcha')}</dd> |
||||
|
</dl> |
||||
|
${input_field(type='submit', value='Submit')} |
||||
|
${input_field(name='cancel', type='submit', value='Cancel')} |
||||
|
</%call> |
@ -0,0 +1,30 @@ |
|||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>${self.page_title()} | RealWorld Benchmark</title> |
||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="contents"> |
||||
|
<div class="header"> |
||||
|
<h1>RealWorld Benchmark</h1> |
||||
|
<blockquote><p> |
||||
|
A less stupid benchmark for Mako and Jinja2 to get an impression how |
||||
|
code changes affect runtime performance. |
||||
|
</p></blockquote> |
||||
|
</div> |
||||
|
<ul class="navigation"> |
||||
|
% for href, caption in page_navigation: |
||||
|
<li><a href="${href|h}">${caption}</a></li> |
||||
|
% endfor |
||||
|
</ul> |
||||
|
<div class="body"> |
||||
|
${self.body()} |
||||
|
</div> |
||||
|
<div class="footer"> |
||||
|
© Copyright 2008 by I don't know who. |
||||
|
</div> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
||||
|
<%def name="page_title()"></%def> |
@ -0,0 +1,112 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
RealWorldish Benchmark |
||||
|
~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
A more real-world benchmark of Jinja2. Like the other benchmark in the |
||||
|
Jinja2 repository this has no real-world usefulnes (despite the name). |
||||
|
Just go away and ignore it. NOW! |
||||
|
|
||||
|
:copyright: (c) 2009 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
import sys |
||||
|
from os.path import join, dirname, abspath |
||||
|
try: |
||||
|
from cProfile import Profile |
||||
|
except ImportError: |
||||
|
from profile import Profile |
||||
|
from pstats import Stats |
||||
|
ROOT = abspath(dirname(__file__)) |
||||
|
|
||||
|
from random import choice, randrange |
||||
|
from datetime import datetime |
||||
|
from timeit import Timer |
||||
|
from jinja2 import Environment, FileSystemLoader |
||||
|
from jinja2.utils import generate_lorem_ipsum |
||||
|
from mako.lookup import TemplateLookup |
||||
|
from genshi.template import TemplateLoader as GenshiTemplateLoader |
||||
|
|
||||
|
|
||||
|
def dateformat(x): |
||||
|
return x.strftime('%Y-%m-%d') |
||||
|
|
||||
|
|
||||
|
jinja_env = Environment(loader=FileSystemLoader(join(ROOT, 'jinja'))) |
||||
|
jinja_env.filters['dateformat'] = dateformat |
||||
|
mako_lookup = TemplateLookup(directories=[join(ROOT, 'mako')]) |
||||
|
genshi_loader = GenshiTemplateLoader([join(ROOT, 'genshi')]) |
||||
|
|
||||
|
class Article(object): |
||||
|
|
||||
|
def __init__(self, id): |
||||
|
self.id = id |
||||
|
self.href = '/article/%d' % self.id |
||||
|
self.title = generate_lorem_ipsum(1, False, 5, 10) |
||||
|
self.user = choice(users) |
||||
|
self.body = generate_lorem_ipsum() |
||||
|
self.pub_date = datetime.utcfromtimestamp(randrange(10 ** 9, 2 * 10 ** 9)) |
||||
|
self.published = True |
||||
|
|
||||
|
|
||||
|
class User(object): |
||||
|
|
||||
|
def __init__(self, username): |
||||
|
self.href = '/user/%s' % username |
||||
|
self.username = username |
||||
|
|
||||
|
|
||||
|
users = map(User, [u'John Doe', u'Jane Doe', u'Peter Somewhat']) |
||||
|
articles = map(Article, range(20)) |
||||
|
navigation = [ |
||||
|
('index', 'Index'), |
||||
|
('about', 'About'), |
||||
|
('foo?bar=1', 'Foo with Bar'), |
||||
|
('foo?bar=2&s=x', 'Foo with X'), |
||||
|
('blah', 'Blub Blah'), |
||||
|
('hehe', 'Haha'), |
||||
|
] * 5 |
||||
|
|
||||
|
context = dict(users=users, articles=articles, page_navigation=navigation) |
||||
|
|
||||
|
|
||||
|
jinja_template = jinja_env.get_template('index.html') |
||||
|
mako_template = mako_lookup.get_template('index.html') |
||||
|
genshi_template = genshi_loader.load('index.html') |
||||
|
|
||||
|
|
||||
|
def test_jinja(): |
||||
|
jinja_template.render(context) |
||||
|
|
||||
|
def test_mako(): |
||||
|
mako_template.render_unicode(**context) |
||||
|
|
||||
|
|
||||
|
from djangoext import django_loader, DjangoContext |
||||
|
def test_django(): |
||||
|
# not cached because django is not thread safe and does |
||||
|
# not cache by itself so it would be unfair to cache it here. |
||||
|
django_template = django_loader.get_template('index.html') |
||||
|
django_template.render(DjangoContext(context)) |
||||
|
|
||||
|
|
||||
|
def test_genshi(): |
||||
|
genshi_template.generate(**context).render('html', doctype='html') |
||||
|
|
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
sys.stdout.write('Realworldish Benchmark:\n') |
||||
|
for test in 'jinja', 'mako', 'django', 'genshi': |
||||
|
t = Timer(setup='from __main__ import test_%s as bench' % test, |
||||
|
stmt='bench()') |
||||
|
sys.stdout.write(' >> %-20s<running>' % test) |
||||
|
sys.stdout.flush() |
||||
|
sys.stdout.write('\r %-20s%.4f seconds\n' % (test, t.timeit(number=200) / 200)) |
||||
|
|
||||
|
if '-p' in sys.argv: |
||||
|
print 'Jinja profile' |
||||
|
p = Profile() |
||||
|
p.runcall(test_jinja) |
||||
|
stats = Stats(p) |
||||
|
stats.sort_stats('time', 'calls') |
||||
|
stats.print_stats() |
@ -0,0 +1,135 @@ |
|||||
|
" Vim syntax file |
||||
|
" Language: Jinja template |
||||
|
" Maintainer: Armin Ronacher <armin.ronacher@active-4.com> |
||||
|
" Last Change: 2008 May 9 |
||||
|
" Version: 1.1 |
||||
|
" |
||||
|
" Known Bugs: |
||||
|
" because of odd limitations dicts and the modulo operator |
||||
|
" appear wrong in the template. |
||||
|
" |
||||
|
" Changes: |
||||
|
" |
||||
|
" 2008 May 9: Added support for Jinja2 changes (new keyword rules) |
||||
|
|
||||
|
" .vimrc variable to disable html highlighting |
||||
|
if !exists('g:jinja_syntax_html') |
||||
|
let g:jinja_syntax_html=1 |
||||
|
endif |
||||
|
|
||||
|
" For version 5.x: Clear all syntax items |
||||
|
" For version 6.x: Quit when a syntax file was already loaded |
||||
|
if !exists("main_syntax") |
||||
|
if version < 600 |
||||
|
syntax clear |
||||
|
elseif exists("b:current_syntax") |
||||
|
finish |
||||
|
endif |
||||
|
let main_syntax = 'jinja' |
||||
|
endif |
||||
|
|
||||
|
" Pull in the HTML syntax. |
||||
|
if g:jinja_syntax_html |
||||
|
if version < 600 |
||||
|
so <sfile>:p:h/html.vim |
||||
|
else |
||||
|
runtime! syntax/html.vim |
||||
|
unlet b:current_syntax |
||||
|
endif |
||||
|
endif |
||||
|
|
||||
|
syntax case match |
||||
|
|
||||
|
" Jinja template built-in tags and parameters (without filter, macro, is and raw, they |
||||
|
" have special threatment) |
||||
|
syn keyword jinjaStatement containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained and if else in not or recursive as import |
||||
|
|
||||
|
syn keyword jinjaStatement containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained is filter skipwhite nextgroup=jinjaFilter |
||||
|
syn keyword jinjaStatement containedin=jinjaTagBlock contained macro skipwhite nextgroup=jinjaFunction |
||||
|
syn keyword jinjaStatement containedin=jinjaTagBlock contained block skipwhite nextgroup=jinjaBlockName |
||||
|
|
||||
|
" Variable Names |
||||
|
syn match jinjaVariable containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[a-zA-Z_][a-zA-Z0-9_]*/ |
||||
|
syn keyword jinjaSpecial containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained false true none False True None loop super caller varargs kwargs |
||||
|
|
||||
|
" Filters |
||||
|
syn match jinjaOperator "|" containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained skipwhite nextgroup=jinjaFilter |
||||
|
syn match jinjaFilter contained /[a-zA-Z_][a-zA-Z0-9_]*/ |
||||
|
syn match jinjaFunction contained /[a-zA-Z_][a-zA-Z0-9_]*/ |
||||
|
syn match jinjaBlockName contained /[a-zA-Z_][a-zA-Z0-9_]*/ |
||||
|
|
||||
|
" Jinja template constants |
||||
|
syn region jinjaString containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained start=/"/ skip=/\(\\\)\@<!\(\(\\\\\)\@>\)*\\"/ end=/"/ |
||||
|
syn region jinjaString containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained start=/'/ skip=/\(\\\)\@<!\(\(\\\\\)\@>\)*\\'/ end=/'/ |
||||
|
syn match jinjaNumber containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[0-9]\+\(\.[0-9]\+\)\?/ |
||||
|
|
||||
|
" Operators |
||||
|
syn match jinjaOperator containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[+\-*\/<>=!,:]/ |
||||
|
syn match jinjaPunctuation containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[()\[\]]/ |
||||
|
syn match jinjaOperator containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /\./ nextgroup=jinjaAttribute |
||||
|
syn match jinjaAttribute contained /[a-zA-Z_][a-zA-Z0-9_]*/ |
||||
|
|
||||
|
" Jinja template tag and variable blocks |
||||
|
syn region jinjaNested matchgroup=jinjaOperator start="(" end=")" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained |
||||
|
syn region jinjaNested matchgroup=jinjaOperator start="\[" end="\]" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained |
||||
|
syn region jinjaNested matchgroup=jinjaOperator start="{" end="}" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained |
||||
|
syn region jinjaTagBlock matchgroup=jinjaTagDelim start=/{%-\?/ end=/-\?%}/ containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaRaw,jinjaString,jinjaNested,jinjaComment |
||||
|
|
||||
|
syn region jinjaVarBlock matchgroup=jinjaVarDelim start=/{{-\?/ end=/-\?}}/ containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaRaw,jinjaString,jinjaNested,jinjaComment |
||||
|
|
||||
|
" Jinja template 'raw' tag |
||||
|
syn region jinjaRaw matchgroup=jinjaRawDelim start="{%\s*raw\s*%}" end="{%\s*endraw\s*%}" containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaString,jinjaComment |
||||
|
|
||||
|
" Jinja comments |
||||
|
syn region jinjaComment matchgroup=jinjaCommentDelim start="{#" end="#}" containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaString |
||||
|
|
||||
|
" Block start keywords. A bit tricker. We only highlight at the start of a |
||||
|
" tag block and only if the name is not followed by a comma or equals sign |
||||
|
" which usually means that we have to deal with an assignment. |
||||
|
syn match jinjaStatement containedin=jinjaTagBlock contained /\({%-\?\s*\)\@<=\<[a-zA-Z_][a-zA-Z0-9_]*\>\(\s*[,=]\)\@!/ |
||||
|
|
||||
|
" and context modifiers |
||||
|
syn match jinjaStatement containedin=jinjaTagBlock contained /\<with\(out\)\?\s\+context\>/ |
||||
|
|
||||
|
|
||||
|
" Define the default highlighting. |
||||
|
" For version 5.7 and earlier: only when not done already |
||||
|
" For version 5.8 and later: only when an item doesn't have highlighting yet |
||||
|
if version >= 508 || !exists("did_jinja_syn_inits") |
||||
|
if version < 508 |
||||
|
let did_jinja_syn_inits = 1 |
||||
|
command -nargs=+ HiLink hi link <args> |
||||
|
else |
||||
|
command -nargs=+ HiLink hi def link <args> |
||||
|
endif |
||||
|
|
||||
|
HiLink jinjaPunctuation jinjaOperator |
||||
|
HiLink jinjaAttribute jinjaVariable |
||||
|
HiLink jinjaFunction jinjaFilter |
||||
|
|
||||
|
HiLink jinjaTagDelim jinjaTagBlock |
||||
|
HiLink jinjaVarDelim jinjaVarBlock |
||||
|
HiLink jinjaCommentDelim jinjaComment |
||||
|
HiLink jinjaRawDelim jinja |
||||
|
|
||||
|
HiLink jinjaSpecial Special |
||||
|
HiLink jinjaOperator Normal |
||||
|
HiLink jinjaRaw Normal |
||||
|
HiLink jinjaTagBlock PreProc |
||||
|
HiLink jinjaVarBlock PreProc |
||||
|
HiLink jinjaStatement Statement |
||||
|
HiLink jinjaFilter Function |
||||
|
HiLink jinjaBlockName Function |
||||
|
HiLink jinjaVariable Identifier |
||||
|
HiLink jinjaString Constant |
||||
|
HiLink jinjaNumber Constant |
||||
|
HiLink jinjaComment Comment |
||||
|
|
||||
|
delcommand HiLink |
||||
|
endif |
||||
|
|
||||
|
let b:current_syntax = "jinja" |
||||
|
|
||||
|
if main_syntax == 'jinja' |
||||
|
unlet main_syntax |
||||
|
endif |
@ -0,0 +1,768 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
Django to Jinja |
||||
|
~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Helper module that can convert django templates into Jinja2 templates. |
||||
|
|
||||
|
This file is not intended to be used as stand alone application but to |
||||
|
be used as library. To convert templates you basically create your own |
||||
|
writer, add extra conversion logic for your custom template tags, |
||||
|
configure your django environment and run the `convert_templates` |
||||
|
function. |
||||
|
|
||||
|
Here a simple example:: |
||||
|
|
||||
|
# configure django (or use settings.configure) |
||||
|
import os |
||||
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'yourapplication.settings' |
||||
|
from yourapplication.foo.templatetags.bar import MyNode |
||||
|
|
||||
|
from django2jinja import Writer, convert_templates |
||||
|
|
||||
|
def write_my_node(writer, node): |
||||
|
writer.start_variable() |
||||
|
writer.write('myfunc(') |
||||
|
for idx, arg in enumerate(node.args): |
||||
|
if idx: |
||||
|
writer.write(', ') |
||||
|
writer.node(arg) |
||||
|
writer.write(')') |
||||
|
writer.end_variable() |
||||
|
|
||||
|
writer = Writer() |
||||
|
writer.node_handlers[MyNode] = write_my_node |
||||
|
convert_templates('/path/to/output/folder', writer=writer) |
||||
|
|
||||
|
Here is an example hos to automatically translate your django |
||||
|
variables to jinja2:: |
||||
|
|
||||
|
import re |
||||
|
# List of tuple (Match pattern, Replace pattern, Exclusion pattern) |
||||
|
|
||||
|
var_re = ((re.compile(r"(u|user)\.is_authenticated"), r"\1.is_authenticated()", None), |
||||
|
(re.compile(r"\.non_field_errors"), r".non_field_errors()", None), |
||||
|
(re.compile(r"\.label_tag"), r".label_tag()", None), |
||||
|
(re.compile(r"\.as_dl"), r".as_dl()", None), |
||||
|
(re.compile(r"\.as_table"), r".as_table()", None), |
||||
|
(re.compile(r"\.as_widget"), r".as_widget()", None), |
||||
|
(re.compile(r"\.as_hidden"), r".as_hidden()", None), |
||||
|
|
||||
|
(re.compile(r"\.get_([0-9_\w]+)_url"), r".get_\1_url()", None), |
||||
|
(re.compile(r"\.url"), r".url()", re.compile(r"(form|calendar).url")), |
||||
|
(re.compile(r"\.get_([0-9_\w]+)_display"), r".get_\1_display()", None), |
||||
|
(re.compile(r"loop\.counter"), r"loop.index", None), |
||||
|
(re.compile(r"loop\.revcounter"), r"loop.revindex", None), |
||||
|
(re.compile(r"request\.GET\.([0-9_\w]+)"), r"request.GET.get('\1', '')", None), |
||||
|
(re.compile(r"request\.get_host"), r"request.get_host()", None), |
||||
|
|
||||
|
(re.compile(r"\.all(?!_)"), r".all()", None), |
||||
|
(re.compile(r"\.all\.0"), r".all()[0]", None), |
||||
|
(re.compile(r"\.([0-9])($|\s+)"), r"[\1]\2", None), |
||||
|
(re.compile(r"\.items"), r".items()", None), |
||||
|
) |
||||
|
writer = Writer(var_re=var_re) |
||||
|
|
||||
|
For details about the writing process have a look at the module code. |
||||
|
|
||||
|
:copyright: (c) 2009 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
import re |
||||
|
import os |
||||
|
import sys |
||||
|
from jinja2.defaults import * |
||||
|
from django.conf import settings |
||||
|
from django.template import defaulttags as core_tags, loader, TextNode, \ |
||||
|
FilterExpression, libraries, Variable, loader_tags, TOKEN_TEXT, \ |
||||
|
TOKEN_VAR |
||||
|
from django.template.debug import DebugVariableNode as VariableNode |
||||
|
from django.templatetags import i18n as i18n_tags |
||||
|
from StringIO import StringIO |
||||
|
|
||||
|
|
||||
|
_node_handlers = {} |
||||
|
_resolved_filters = {} |
||||
|
_newline_re = re.compile(r'(?:\r\n|\r|\n)') |
||||
|
|
||||
|
|
||||
|
# Django stores an itertools object on the cycle node. Not only is this |
||||
|
# thread unsafe but also a problem for the converter which needs the raw |
||||
|
# string values passed to the constructor to create a jinja loop.cycle() |
||||
|
# call from it. |
||||
|
_old_cycle_init = core_tags.CycleNode.__init__ |
||||
|
def _fixed_cycle_init(self, cyclevars, variable_name=None): |
||||
|
self.raw_cycle_vars = map(Variable, cyclevars) |
||||
|
_old_cycle_init(self, cyclevars, variable_name) |
||||
|
core_tags.CycleNode.__init__ = _fixed_cycle_init |
||||
|
|
||||
|
|
||||
|
def node(cls): |
||||
|
def proxy(f): |
||||
|
_node_handlers[cls] = f |
||||
|
return f |
||||
|
return proxy |
||||
|
|
||||
|
|
||||
|
def convert_templates(output_dir, extensions=('.html', '.txt'), writer=None, |
||||
|
callback=None): |
||||
|
"""Iterates over all templates in the template dirs configured and |
||||
|
translates them and writes the new templates into the output directory. |
||||
|
""" |
||||
|
if writer is None: |
||||
|
writer = Writer() |
||||
|
|
||||
|
def filter_templates(files): |
||||
|
for filename in files: |
||||
|
ifilename = filename.lower() |
||||
|
for extension in extensions: |
||||
|
if ifilename.endswith(extension): |
||||
|
yield filename |
||||
|
|
||||
|
def translate(f, loadname): |
||||
|
template = loader.get_template(loadname) |
||||
|
original = writer.stream |
||||
|
writer.stream = f |
||||
|
writer.body(template.nodelist) |
||||
|
writer.stream = original |
||||
|
|
||||
|
if callback is None: |
||||
|
def callback(template): |
||||
|
print template |
||||
|
|
||||
|
for directory in settings.TEMPLATE_DIRS: |
||||
|
for dirname, _, files in os.walk(directory): |
||||
|
dirname = dirname[len(directory) + 1:] |
||||
|
for filename in filter_templates(files): |
||||
|
source = os.path.normpath(os.path.join(dirname, filename)) |
||||
|
target = os.path.join(output_dir, dirname, filename) |
||||
|
basetarget = os.path.dirname(target) |
||||
|
if not os.path.exists(basetarget): |
||||
|
os.makedirs(basetarget) |
||||
|
callback(source) |
||||
|
f = file(target, 'w') |
||||
|
try: |
||||
|
translate(f, source) |
||||
|
finally: |
||||
|
f.close() |
||||
|
|
||||
|
|
||||
|
class Writer(object): |
||||
|
"""The core writer class.""" |
||||
|
|
||||
|
def __init__(self, stream=None, error_stream=None, |
||||
|
block_start_string=BLOCK_START_STRING, |
||||
|
block_end_string=BLOCK_END_STRING, |
||||
|
variable_start_string=VARIABLE_START_STRING, |
||||
|
variable_end_string=VARIABLE_END_STRING, |
||||
|
comment_start_string=COMMENT_START_STRING, |
||||
|
comment_end_string=COMMENT_END_STRING, |
||||
|
initial_autoescape=True, |
||||
|
use_jinja_autoescape=False, |
||||
|
custom_node_handlers=None, |
||||
|
var_re=[], |
||||
|
env=None): |
||||
|
if stream is None: |
||||
|
stream = sys.stdout |
||||
|
if error_stream is None: |
||||
|
error_stream = sys.stderr |
||||
|
self.stream = stream |
||||
|
self.error_stream = error_stream |
||||
|
self.block_start_string = block_start_string |
||||
|
self.block_end_string = block_end_string |
||||
|
self.variable_start_string = variable_start_string |
||||
|
self.variable_end_string = variable_end_string |
||||
|
self.comment_start_string = comment_start_string |
||||
|
self.comment_end_string = comment_end_string |
||||
|
self.autoescape = initial_autoescape |
||||
|
self.spaceless = False |
||||
|
self.use_jinja_autoescape = use_jinja_autoescape |
||||
|
self.node_handlers = dict(_node_handlers, |
||||
|
**(custom_node_handlers or {})) |
||||
|
self._loop_depth = 0 |
||||
|
self._filters_warned = set() |
||||
|
self.var_re = var_re |
||||
|
self.env = env |
||||
|
|
||||
|
def enter_loop(self): |
||||
|
"""Increments the loop depth so that write functions know if they |
||||
|
are in a loop. |
||||
|
""" |
||||
|
self._loop_depth += 1 |
||||
|
|
||||
|
def leave_loop(self): |
||||
|
"""Reverse of enter_loop.""" |
||||
|
self._loop_depth -= 1 |
||||
|
|
||||
|
@property |
||||
|
def in_loop(self): |
||||
|
"""True if we are in a loop.""" |
||||
|
return self._loop_depth > 0 |
||||
|
|
||||
|
def write(self, s): |
||||
|
"""Writes stuff to the stream.""" |
||||
|
self.stream.write(s.encode(settings.FILE_CHARSET)) |
||||
|
|
||||
|
def print_expr(self, expr): |
||||
|
"""Open a variable tag, write to the string to the stream and close.""" |
||||
|
self.start_variable() |
||||
|
self.write(expr) |
||||
|
self.end_variable() |
||||
|
|
||||
|
def _post_open(self): |
||||
|
if self.spaceless: |
||||
|
self.write('- ') |
||||
|
else: |
||||
|
self.write(' ') |
||||
|
|
||||
|
def _pre_close(self): |
||||
|
if self.spaceless: |
||||
|
self.write(' -') |
||||
|
else: |
||||
|
self.write(' ') |
||||
|
|
||||
|
def start_variable(self): |
||||
|
"""Start a variable.""" |
||||
|
self.write(self.variable_start_string) |
||||
|
self._post_open() |
||||
|
|
||||
|
def end_variable(self, always_safe=False): |
||||
|
"""End a variable.""" |
||||
|
if not always_safe and self.autoescape and \ |
||||
|
not self.use_jinja_autoescape: |
||||
|
self.write('|e') |
||||
|
self._pre_close() |
||||
|
self.write(self.variable_end_string) |
||||
|
|
||||
|
def start_block(self): |
||||
|
"""Starts a block.""" |
||||
|
self.write(self.block_start_string) |
||||
|
self._post_open() |
||||
|
|
||||
|
def end_block(self): |
||||
|
"""Ends a block.""" |
||||
|
self._pre_close() |
||||
|
self.write(self.block_end_string) |
||||
|
|
||||
|
def tag(self, name): |
||||
|
"""Like `print_expr` just for blocks.""" |
||||
|
self.start_block() |
||||
|
self.write(name) |
||||
|
self.end_block() |
||||
|
|
||||
|
def variable(self, name): |
||||
|
"""Prints a variable. This performs variable name transformation.""" |
||||
|
self.write(self.translate_variable_name(name)) |
||||
|
|
||||
|
def literal(self, value): |
||||
|
"""Writes a value as literal.""" |
||||
|
value = repr(value) |
||||
|
if value[:2] in ('u"', "u'"): |
||||
|
value = value[1:] |
||||
|
self.write(value) |
||||
|
|
||||
|
def filters(self, filters, is_block=False): |
||||
|
"""Dumps a list of filters.""" |
||||
|
want_pipe = not is_block |
||||
|
for filter, args in filters: |
||||
|
name = self.get_filter_name(filter) |
||||
|
if name is None: |
||||
|
self.warn('Could not find filter %s' % name) |
||||
|
continue |
||||
|
if name not in DEFAULT_FILTERS and \ |
||||
|
name not in self._filters_warned: |
||||
|
self._filters_warned.add(name) |
||||
|
self.warn('Filter %s probably doesn\'t exist in Jinja' % |
||||
|
name) |
||||
|
if not want_pipe: |
||||
|
want_pipe = True |
||||
|
else: |
||||
|
self.write('|') |
||||
|
self.write(name) |
||||
|
if args: |
||||
|
self.write('(') |
||||
|
for idx, (is_var, value) in enumerate(args): |
||||
|
if idx: |
||||
|
self.write(', ') |
||||
|
if is_var: |
||||
|
self.node(value) |
||||
|
else: |
||||
|
self.literal(value) |
||||
|
self.write(')') |
||||
|
|
||||
|
def get_location(self, origin, position): |
||||
|
"""Returns the location for an origin and position tuple as name |
||||
|
and lineno. |
||||
|
""" |
||||
|
if hasattr(origin, 'source'): |
||||
|
source = origin.source |
||||
|
name = '<unknown source>' |
||||
|
else: |
||||
|
source = origin.loader(origin.loadname, origin.dirs)[0] |
||||
|
name = origin.loadname |
||||
|
lineno = len(_newline_re.findall(source[:position[0]])) + 1 |
||||
|
return name, lineno |
||||
|
|
||||
|
def warn(self, message, node=None): |
||||
|
"""Prints a warning to the error stream.""" |
||||
|
if node is not None and hasattr(node, 'source'): |
||||
|
filename, lineno = self.get_location(*node.source) |
||||
|
message = '[%s:%d] %s' % (filename, lineno, message) |
||||
|
print >> self.error_stream, message |
||||
|
|
||||
|
def translate_variable_name(self, var): |
||||
|
"""Performs variable name translation.""" |
||||
|
if self.in_loop and var == 'forloop' or var.startswith('forloop.'): |
||||
|
var = var[3:] |
||||
|
|
||||
|
for reg, rep, unless in self.var_re: |
||||
|
no_unless = unless and unless.search(var) or True |
||||
|
if reg.search(var) and no_unless: |
||||
|
var = reg.sub(rep, var) |
||||
|
break |
||||
|
return var |
||||
|
|
||||
|
def get_filter_name(self, filter): |
||||
|
"""Returns the filter name for a filter function or `None` if there |
||||
|
is no such filter. |
||||
|
""" |
||||
|
if filter not in _resolved_filters: |
||||
|
for library in libraries.values(): |
||||
|
for key, value in library.filters.iteritems(): |
||||
|
_resolved_filters[value] = key |
||||
|
return _resolved_filters.get(filter, None) |
||||
|
|
||||
|
def node(self, node): |
||||
|
"""Invokes the node handler for a node.""" |
||||
|
for cls, handler in self.node_handlers.iteritems(): |
||||
|
if type(node) is cls or type(node).__name__ == cls: |
||||
|
handler(self, node) |
||||
|
break |
||||
|
else: |
||||
|
self.warn('Untranslatable node %s.%s found' % ( |
||||
|
node.__module__, |
||||
|
node.__class__.__name__ |
||||
|
), node) |
||||
|
|
||||
|
def body(self, nodes): |
||||
|
"""Calls node() for every node in the iterable passed.""" |
||||
|
for node in nodes: |
||||
|
self.node(node) |
||||
|
|
||||
|
|
||||
|
@node(TextNode) |
||||
|
def text_node(writer, node): |
||||
|
writer.write(node.s) |
||||
|
|
||||
|
|
||||
|
@node(Variable) |
||||
|
def variable(writer, node): |
||||
|
if node.translate: |
||||
|
writer.warn('i18n system used, make sure to install translations', node) |
||||
|
writer.write('_(') |
||||
|
if node.literal is not None: |
||||
|
writer.literal(node.literal) |
||||
|
else: |
||||
|
writer.variable(node.var) |
||||
|
if node.translate: |
||||
|
writer.write(')') |
||||
|
|
||||
|
|
||||
|
@node(VariableNode) |
||||
|
def variable_node(writer, node): |
||||
|
writer.start_variable() |
||||
|
if node.filter_expression.var.var == 'block.super' \ |
||||
|
and not node.filter_expression.filters: |
||||
|
writer.write('super()') |
||||
|
else: |
||||
|
writer.node(node.filter_expression) |
||||
|
writer.end_variable() |
||||
|
|
||||
|
|
||||
|
@node(FilterExpression) |
||||
|
def filter_expression(writer, node): |
||||
|
writer.node(node.var) |
||||
|
writer.filters(node.filters) |
||||
|
|
||||
|
|
||||
|
@node(core_tags.CommentNode) |
||||
|
def comment_tag(writer, node): |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
@node(core_tags.DebugNode) |
||||
|
def comment_tag(writer, node): |
||||
|
writer.warn('Debug tag detected. Make sure to add a global function ' |
||||
|
'called debug to the namespace.', node=node) |
||||
|
writer.print_expr('debug()') |
||||
|
|
||||
|
|
||||
|
@node(core_tags.ForNode) |
||||
|
def for_loop(writer, node): |
||||
|
writer.start_block() |
||||
|
writer.write('for ') |
||||
|
for idx, var in enumerate(node.loopvars): |
||||
|
if idx: |
||||
|
writer.write(', ') |
||||
|
writer.variable(var) |
||||
|
writer.write(' in ') |
||||
|
if node.is_reversed: |
||||
|
writer.write('(') |
||||
|
writer.node(node.sequence) |
||||
|
if node.is_reversed: |
||||
|
writer.write(')|reverse') |
||||
|
writer.end_block() |
||||
|
writer.enter_loop() |
||||
|
writer.body(node.nodelist_loop) |
||||
|
writer.leave_loop() |
||||
|
writer.tag('endfor') |
||||
|
|
||||
|
|
||||
|
@node(core_tags.IfNode) |
||||
|
def if_condition(writer, node): |
||||
|
writer.start_block() |
||||
|
writer.write('if ') |
||||
|
join_with = 'and' |
||||
|
if node.link_type == core_tags.IfNode.LinkTypes.or_: |
||||
|
join_with = 'or' |
||||
|
|
||||
|
for idx, (ifnot, expr) in enumerate(node.bool_exprs): |
||||
|
if idx: |
||||
|
writer.write(' %s ' % join_with) |
||||
|
if ifnot: |
||||
|
writer.write('not ') |
||||
|
writer.node(expr) |
||||
|
writer.end_block() |
||||
|
writer.body(node.nodelist_true) |
||||
|
if node.nodelist_false: |
||||
|
writer.tag('else') |
||||
|
writer.body(node.nodelist_false) |
||||
|
writer.tag('endif') |
||||
|
|
||||
|
|
||||
|
@node(core_tags.IfEqualNode) |
||||
|
def if_equal(writer, node): |
||||
|
writer.start_block() |
||||
|
writer.write('if ') |
||||
|
writer.node(node.var1) |
||||
|
if node.negate: |
||||
|
writer.write(' != ') |
||||
|
else: |
||||
|
writer.write(' == ') |
||||
|
writer.node(node.var2) |
||||
|
writer.end_block() |
||||
|
writer.body(node.nodelist_true) |
||||
|
if node.nodelist_false: |
||||
|
writer.tag('else') |
||||
|
writer.body(node.nodelist_false) |
||||
|
writer.tag('endif') |
||||
|
|
||||
|
|
||||
|
@node(loader_tags.BlockNode) |
||||
|
def block(writer, node): |
||||
|
writer.tag('block ' + node.name.replace('-', '_').rstrip('_')) |
||||
|
node = node |
||||
|
while node.parent is not None: |
||||
|
node = node.parent |
||||
|
writer.body(node.nodelist) |
||||
|
writer.tag('endblock') |
||||
|
|
||||
|
|
||||
|
@node(loader_tags.ExtendsNode) |
||||
|
def extends(writer, node): |
||||
|
writer.start_block() |
||||
|
writer.write('extends ') |
||||
|
if node.parent_name_expr: |
||||
|
writer.node(node.parent_name_expr) |
||||
|
else: |
||||
|
writer.literal(node.parent_name) |
||||
|
writer.end_block() |
||||
|
writer.body(node.nodelist) |
||||
|
|
||||
|
|
||||
|
@node(loader_tags.ConstantIncludeNode) |
||||
|
@node(loader_tags.IncludeNode) |
||||
|
def include(writer, node): |
||||
|
writer.start_block() |
||||
|
writer.write('include ') |
||||
|
if hasattr(node, 'template'): |
||||
|
writer.literal(node.template.name) |
||||
|
else: |
||||
|
writer.node(node.template_name) |
||||
|
writer.end_block() |
||||
|
|
||||
|
|
||||
|
@node(core_tags.CycleNode) |
||||
|
def cycle(writer, node): |
||||
|
if not writer.in_loop: |
||||
|
writer.warn('Untranslatable free cycle (cycle outside loop)', node=node) |
||||
|
return |
||||
|
if node.variable_name is not None: |
||||
|
writer.start_block() |
||||
|
writer.write('set %s = ' % node.variable_name) |
||||
|
else: |
||||
|
writer.start_variable() |
||||
|
writer.write('loop.cycle(') |
||||
|
for idx, var in enumerate(node.raw_cycle_vars): |
||||
|
if idx: |
||||
|
writer.write(', ') |
||||
|
writer.node(var) |
||||
|
writer.write(')') |
||||
|
if node.variable_name is not None: |
||||
|
writer.end_block() |
||||
|
else: |
||||
|
writer.end_variable() |
||||
|
|
||||
|
|
||||
|
@node(core_tags.FilterNode) |
||||
|
def filter(writer, node): |
||||
|
writer.start_block() |
||||
|
writer.write('filter ') |
||||
|
writer.filters(node.filter_expr.filters, True) |
||||
|
writer.end_block() |
||||
|
writer.body(node.nodelist) |
||||
|
writer.tag('endfilter') |
||||
|
|
||||
|
|
||||
|
@node(core_tags.AutoEscapeControlNode) |
||||
|
def autoescape_control(writer, node): |
||||
|
original = writer.autoescape |
||||
|
writer.autoescape = node.setting |
||||
|
writer.body(node.nodelist) |
||||
|
writer.autoescape = original |
||||
|
|
||||
|
|
||||
|
@node(core_tags.SpacelessNode) |
||||
|
def spaceless(writer, node): |
||||
|
original = writer.spaceless |
||||
|
writer.spaceless = True |
||||
|
writer.warn('entering spaceless mode with different semantics', node) |
||||
|
# do the initial stripping |
||||
|
nodelist = list(node.nodelist) |
||||
|
if nodelist: |
||||
|
if isinstance(nodelist[0], TextNode): |
||||
|
nodelist[0] = TextNode(nodelist[0].s.lstrip()) |
||||
|
if isinstance(nodelist[-1], TextNode): |
||||
|
nodelist[-1] = TextNode(nodelist[-1].s.rstrip()) |
||||
|
writer.body(nodelist) |
||||
|
writer.spaceless = original |
||||
|
|
||||
|
|
||||
|
@node(core_tags.TemplateTagNode) |
||||
|
def template_tag(writer, node): |
||||
|
tag = { |
||||
|
'openblock': writer.block_start_string, |
||||
|
'closeblock': writer.block_end_string, |
||||
|
'openvariable': writer.variable_start_string, |
||||
|
'closevariable': writer.variable_end_string, |
||||
|
'opencomment': writer.comment_start_string, |
||||
|
'closecomment': writer.comment_end_string, |
||||
|
'openbrace': '{', |
||||
|
'closebrace': '}' |
||||
|
}.get(node.tagtype) |
||||
|
if tag: |
||||
|
writer.start_variable() |
||||
|
writer.literal(tag) |
||||
|
writer.end_variable() |
||||
|
|
||||
|
|
||||
|
@node(core_tags.URLNode) |
||||
|
def url_tag(writer, node): |
||||
|
writer.warn('url node used. make sure to provide a proper url() ' |
||||
|
'function', node) |
||||
|
if node.asvar: |
||||
|
writer.start_block() |
||||
|
writer.write('set %s = ' % node.asvar) |
||||
|
else: |
||||
|
writer.start_variable() |
||||
|
autoescape = writer.autoescape |
||||
|
writer.write('url(') |
||||
|
writer.literal(node.view_name) |
||||
|
for arg in node.args: |
||||
|
writer.write(', ') |
||||
|
writer.node(arg) |
||||
|
for key, arg in node.kwargs.items(): |
||||
|
writer.write(', %s=' % key) |
||||
|
writer.node(arg) |
||||
|
writer.write(')') |
||||
|
if node.asvar: |
||||
|
writer.end_block() |
||||
|
else: |
||||
|
writer.end_variable() |
||||
|
|
||||
|
|
||||
|
@node(core_tags.WidthRatioNode) |
||||
|
def width_ratio(writer, node): |
||||
|
writer.warn('widthratio expanded into formula. You may want to provide ' |
||||
|
'a helper function for this calculation', node) |
||||
|
writer.start_variable() |
||||
|
writer.write('(') |
||||
|
writer.node(node.val_expr) |
||||
|
writer.write(' / ') |
||||
|
writer.node(node.max_expr) |
||||
|
writer.write(' * ') |
||||
|
writer.write(str(int(node.max_width))) |
||||
|
writer.write(')|round|int') |
||||
|
writer.end_variable(always_safe=True) |
||||
|
|
||||
|
|
||||
|
@node(core_tags.WithNode) |
||||
|
def with_block(writer, node): |
||||
|
writer.warn('with block expanded into set statement. This could cause ' |
||||
|
'variables following that block to be overridden.', node) |
||||
|
writer.start_block() |
||||
|
writer.write('set %s = ' % node.name) |
||||
|
writer.node(node.var) |
||||
|
writer.end_block() |
||||
|
writer.body(node.nodelist) |
||||
|
|
||||
|
|
||||
|
@node(core_tags.RegroupNode) |
||||
|
def regroup(writer, node): |
||||
|
if node.expression.var.literal: |
||||
|
writer.warn('literal in groupby filter used. Behavior in that ' |
||||
|
'situation is undefined and translation is skipped.', node) |
||||
|
return |
||||
|
elif node.expression.filters: |
||||
|
writer.warn('filters in groupby filter used. Behavior in that ' |
||||
|
'situation is undefined which is most likely a bug ' |
||||
|
'in your code. Filters were ignored.', node) |
||||
|
writer.start_block() |
||||
|
writer.write('set %s = ' % node.var_name) |
||||
|
writer.node(node.target) |
||||
|
writer.write('|groupby(') |
||||
|
writer.literal(node.expression.var.var) |
||||
|
writer.write(')') |
||||
|
writer.end_block() |
||||
|
|
||||
|
|
||||
|
@node(core_tags.LoadNode) |
||||
|
def warn_load(writer, node): |
||||
|
writer.warn('load statement used which was ignored on conversion', node) |
||||
|
|
||||
|
|
||||
|
@node(i18n_tags.GetAvailableLanguagesNode) |
||||
|
def get_available_languages(writer, node): |
||||
|
writer.warn('make sure to provide a get_available_languages function', node) |
||||
|
writer.tag('set %s = get_available_languages()' % |
||||
|
writer.translate_variable_name(node.variable)) |
||||
|
|
||||
|
|
||||
|
@node(i18n_tags.GetCurrentLanguageNode) |
||||
|
def get_current_language(writer, node): |
||||
|
writer.warn('make sure to provide a get_current_language function', node) |
||||
|
writer.tag('set %s = get_current_language()' % |
||||
|
writer.translate_variable_name(node.variable)) |
||||
|
|
||||
|
|
||||
|
@node(i18n_tags.GetCurrentLanguageBidiNode) |
||||
|
def get_current_language_bidi(writer, node): |
||||
|
writer.warn('make sure to provide a get_current_language_bidi function', node) |
||||
|
writer.tag('set %s = get_current_language_bidi()' % |
||||
|
writer.translate_variable_name(node.variable)) |
||||
|
|
||||
|
|
||||
|
@node(i18n_tags.TranslateNode) |
||||
|
def simple_gettext(writer, node): |
||||
|
writer.warn('i18n system used, make sure to install translations', node) |
||||
|
writer.start_variable() |
||||
|
writer.write('_(') |
||||
|
writer.node(node.value) |
||||
|
writer.write(')') |
||||
|
writer.end_variable() |
||||
|
|
||||
|
|
||||
|
@node(i18n_tags.BlockTranslateNode) |
||||
|
def translate_block(writer, node): |
||||
|
first_var = [] |
||||
|
variables = set() |
||||
|
|
||||
|
def touch_var(name): |
||||
|
variables.add(name) |
||||
|
if not first_var: |
||||
|
first_var.append(name) |
||||
|
|
||||
|
def dump_token_list(tokens): |
||||
|
for token in tokens: |
||||
|
if token.token_type == TOKEN_TEXT: |
||||
|
writer.write(token.contents) |
||||
|
elif token.token_type == TOKEN_VAR: |
||||
|
writer.print_expr(token.contents) |
||||
|
touch_var(token.contents) |
||||
|
|
||||
|
writer.warn('i18n system used, make sure to install translations', node) |
||||
|
writer.start_block() |
||||
|
writer.write('trans') |
||||
|
idx = -1 |
||||
|
for idx, (key, var) in enumerate(node.extra_context.items()): |
||||
|
if idx: |
||||
|
writer.write(',') |
||||
|
writer.write(' %s=' % key) |
||||
|
touch_var(key) |
||||
|
writer.node(var.filter_expression) |
||||
|
|
||||
|
have_plural = False |
||||
|
plural_var = None |
||||
|
if node.plural and node.countervar and node.counter: |
||||
|
have_plural = True |
||||
|
plural_var = node.countervar |
||||
|
if plural_var not in variables: |
||||
|
if idx > -1: |
||||
|
writer.write(',') |
||||
|
touch_var(plural_var) |
||||
|
writer.write(' %s=' % plural_var) |
||||
|
writer.node(node.counter) |
||||
|
|
||||
|
writer.end_block() |
||||
|
dump_token_list(node.singular) |
||||
|
if node.plural and node.countervar and node.counter: |
||||
|
writer.start_block() |
||||
|
writer.write('pluralize') |
||||
|
if node.countervar != first_var[0]: |
||||
|
writer.write(' ' + node.countervar) |
||||
|
writer.end_block() |
||||
|
dump_token_list(node.plural) |
||||
|
writer.tag('endtrans') |
||||
|
|
||||
|
@node("SimpleNode") |
||||
|
def simple_tag(writer, node): |
||||
|
"""Check if the simple tag exist as a filter in """ |
||||
|
name = node.tag_name |
||||
|
if writer.env and \ |
||||
|
name not in writer.env.filters and \ |
||||
|
name not in writer._filters_warned: |
||||
|
writer._filters_warned.add(name) |
||||
|
writer.warn('Filter %s probably doesn\'t exist in Jinja' % |
||||
|
name) |
||||
|
|
||||
|
if not node.vars_to_resolve: |
||||
|
# No argument, pass the request |
||||
|
writer.start_variable() |
||||
|
writer.write('request|') |
||||
|
writer.write(name) |
||||
|
writer.end_variable() |
||||
|
return |
||||
|
|
||||
|
first_var = node.vars_to_resolve[0] |
||||
|
args = node.vars_to_resolve[1:] |
||||
|
writer.start_variable() |
||||
|
|
||||
|
# Copied from Writer.filters() |
||||
|
writer.node(first_var) |
||||
|
|
||||
|
writer.write('|') |
||||
|
writer.write(name) |
||||
|
if args: |
||||
|
writer.write('(') |
||||
|
for idx, var in enumerate(args): |
||||
|
if idx: |
||||
|
writer.write(', ') |
||||
|
if var.var: |
||||
|
writer.node(var) |
||||
|
else: |
||||
|
writer.literal(var.literal) |
||||
|
writer.write(')') |
||||
|
writer.end_variable() |
||||
|
|
||||
|
# get rid of node now, it shouldn't be used normally |
||||
|
del node |
@ -0,0 +1,7 @@ |
|||||
|
from django.conf import settings |
||||
|
settings.configure(TEMPLATE_DIRS=['templates'], TEMPLATE_DEBUG=True) |
||||
|
|
||||
|
from django2jinja import convert_templates, Writer |
||||
|
|
||||
|
writer = Writer(use_jinja_autoescape=True) |
||||
|
convert_templates('converted', writer=writer) |
@ -0,0 +1,58 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% load i18n %} |
||||
|
{% block title %}Foo{% endblock %} |
||||
|
{% block page-body %} |
||||
|
{{ block.super }} |
||||
|
Hello {{ name|cut:"d" }}! |
||||
|
|
||||
|
{% for item in seq reversed %} |
||||
|
{% if forloop.index|divisibleby:2 %} |
||||
|
<li class="{% cycle 'a' 'b' %}">{{ item }}</li> |
||||
|
{% endif %} |
||||
|
{% endfor %} |
||||
|
{% ifequal foo bar %} |
||||
|
haha |
||||
|
{% else %} |
||||
|
hmm |
||||
|
{% endifequal %} |
||||
|
{% filter upper %} |
||||
|
{% include "subtemplate.html" %} |
||||
|
{% include foo %} |
||||
|
{% endfilter %} |
||||
|
{% spaceless %} |
||||
|
Hello World |
||||
|
{{ foo }} |
||||
|
Hmm |
||||
|
{% endspaceless %} |
||||
|
{% templatetag opencomment %}...{% templatetag closecomment %} |
||||
|
{% url foo a, b, c=d %} |
||||
|
{% url foo a, b, c=d as hmm %} |
||||
|
|
||||
|
{% with object.value as value %} |
||||
|
<img src='bar.gif' height='10' width='{% widthratio value 200 100 %}'> |
||||
|
{% endwith %} |
||||
|
|
||||
|
<pre>{% debug %}</pre> |
||||
|
|
||||
|
{% blocktrans with book|title as book_t and author|title as author_t %} |
||||
|
This is {{ book_t }} by {{ author_t }} |
||||
|
{% endblocktrans %} |
||||
|
|
||||
|
{% blocktrans count list|length as counter %} |
||||
|
There is only one {{ name }} object. |
||||
|
{% plural %} |
||||
|
There are {{ counter }} {{ name }} objects. |
||||
|
{% endblocktrans %} |
||||
|
|
||||
|
{% blocktrans with name|escape as name count list|length as counter %} |
||||
|
There is only one {{ name }} object. |
||||
|
{% plural %} |
||||
|
There are {{ counter }} {{ name }} objects. |
||||
|
{% endblocktrans %} |
||||
|
|
||||
|
{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %} |
||||
|
|
||||
|
<p>{% trans "This is the title." %}</p> |
||||
|
|
||||
|
{% regroup people by gender as grouped %} |
||||
|
{% endblock %} |
@ -0,0 +1,4 @@ |
|||||
|
<title>{% block title %}{% endblock %}</title> |
||||
|
<div class="body"> |
||||
|
{% block page-body %}{% endblock %} |
||||
|
</div> |
@ -0,0 +1 @@ |
|||||
|
Hello World! |
@ -0,0 +1,86 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
djangojinja2 |
||||
|
~~~~~~~~~~~~ |
||||
|
|
||||
|
Adds support for Jinja2 to Django. |
||||
|
|
||||
|
Configuration variables: |
||||
|
|
||||
|
======================= ============================================= |
||||
|
Key Description |
||||
|
======================= ============================================= |
||||
|
`JINJA2_TEMPLATE_DIRS` List of template folders |
||||
|
`JINJA2_EXTENSIONS` List of Jinja2 extensions to use |
||||
|
`JINJA2_CACHE_SIZE` The size of the Jinja2 template cache. |
||||
|
======================= ============================================= |
||||
|
|
||||
|
:copyright: (c) 2009 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
from itertools import chain |
||||
|
from django.conf import settings |
||||
|
from django.http import HttpResponse |
||||
|
from django.core.exceptions import ImproperlyConfigured |
||||
|
from django.template.context import get_standard_processors |
||||
|
from django.template import TemplateDoesNotExist |
||||
|
from jinja2 import Environment, FileSystemLoader, TemplateNotFound |
||||
|
from jinja2.defaults import DEFAULT_NAMESPACE |
||||
|
|
||||
|
|
||||
|
# the environment is unconfigured until the first template is loaded. |
||||
|
_jinja_env = None |
||||
|
|
||||
|
|
||||
|
def get_env(): |
||||
|
"""Get the Jinja2 env and initialize it if necessary.""" |
||||
|
global _jinja_env |
||||
|
if _jinja_env is None: |
||||
|
_jinja_env = create_env() |
||||
|
return _jinja_env |
||||
|
|
||||
|
|
||||
|
def create_env(): |
||||
|
"""Create a new Jinja2 environment.""" |
||||
|
searchpath = list(settings.JINJA2_TEMPLATE_DIRS) |
||||
|
return Environment(loader=FileSystemLoader(searchpath), |
||||
|
auto_reload=settings.TEMPLATE_DEBUG, |
||||
|
cache_size=getattr(settings, 'JINJA2_CACHE_SIZE', 400), |
||||
|
extensions=getattr(settings, 'JINJA2_EXTENSIONS', ())) |
||||
|
|
||||
|
|
||||
|
def get_template(template_name, globals=None): |
||||
|
"""Load a template.""" |
||||
|
try: |
||||
|
return get_env().get_template(template_name, globals=globals) |
||||
|
except TemplateNotFound, e: |
||||
|
raise TemplateDoesNotExist(str(e)) |
||||
|
|
||||
|
|
||||
|
def select_template(templates, globals=None): |
||||
|
"""Try to load one of the given templates.""" |
||||
|
env = get_env() |
||||
|
for template in templates: |
||||
|
try: |
||||
|
return env.get_template(template, globals=globals) |
||||
|
except TemplateNotFound: |
||||
|
continue |
||||
|
raise TemplateDoesNotExist(', '.join(templates)) |
||||
|
|
||||
|
|
||||
|
def render_to_string(template_name, context=None, request=None, |
||||
|
processors=None): |
||||
|
"""Render a template into a string.""" |
||||
|
context = dict(context or {}) |
||||
|
if request is not None: |
||||
|
context['request'] = request |
||||
|
for processor in chain(get_standard_processors(), processors or ()): |
||||
|
context.update(processor(request)) |
||||
|
return get_template(template_name).render(context) |
||||
|
|
||||
|
|
||||
|
def render_to_response(template_name, context=None, request=None, |
||||
|
processors=None, mimetype=None): |
||||
|
"""Render a template into a response object.""" |
||||
|
return HttpResponse(render_to_string(template_name, context, request, |
||||
|
processors), mimetype=mimetype) |
@ -0,0 +1,78 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
Inline Gettext |
||||
|
~~~~~~~~~~~~~~ |
||||
|
|
||||
|
An example extension for Jinja2 that supports inline gettext calls. |
||||
|
Requires the i18n extension to be loaded. |
||||
|
|
||||
|
:copyright: (c) 2009 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
import re |
||||
|
from jinja2.ext import Extension |
||||
|
from jinja2.lexer import Token, count_newlines |
||||
|
from jinja2.exceptions import TemplateSyntaxError |
||||
|
|
||||
|
|
||||
|
_outside_re = re.compile(r'\\?(gettext|_)\(') |
||||
|
_inside_re = re.compile(r'\\?[()]') |
||||
|
|
||||
|
|
||||
|
class InlineGettext(Extension): |
||||
|
"""This extension implements support for inline gettext blocks:: |
||||
|
|
||||
|
<h1>_(Welcome)</h1> |
||||
|
<p>_(This is a paragraph)</p> |
||||
|
|
||||
|
Requires the i18n extension to be loaded and configured. |
||||
|
""" |
||||
|
|
||||
|
def filter_stream(self, stream): |
||||
|
paren_stack = 0 |
||||
|
|
||||
|
for token in stream: |
||||
|
if token.type is not 'data': |
||||
|
yield token |
||||
|
continue |
||||
|
|
||||
|
pos = 0 |
||||
|
lineno = token.lineno |
||||
|
|
||||
|
while 1: |
||||
|
if not paren_stack: |
||||
|
match = _outside_re.search(token.value, pos) |
||||
|
else: |
||||
|
match = _inside_re.search(token.value, pos) |
||||
|
if match is None: |
||||
|
break |
||||
|
new_pos = match.start() |
||||
|
if new_pos > pos: |
||||
|
preval = token.value[pos:new_pos] |
||||
|
yield Token(lineno, 'data', preval) |
||||
|
lineno += count_newlines(preval) |
||||
|
gtok = match.group() |
||||
|
if gtok[0] == '\\': |
||||
|
yield Token(lineno, 'data', gtok[1:]) |
||||
|
elif not paren_stack: |
||||
|
yield Token(lineno, 'block_begin', None) |
||||
|
yield Token(lineno, 'name', 'trans') |
||||
|
yield Token(lineno, 'block_end', None) |
||||
|
paren_stack = 1 |
||||
|
else: |
||||
|
if gtok == '(' or paren_stack > 1: |
||||
|
yield Token(lineno, 'data', gtok) |
||||
|
paren_stack += gtok == ')' and -1 or 1 |
||||
|
if not paren_stack: |
||||
|
yield Token(lineno, 'block_begin', None) |
||||
|
yield Token(lineno, 'name', 'endtrans') |
||||
|
yield Token(lineno, 'block_end', None) |
||||
|
pos = match.end() |
||||
|
|
||||
|
if pos < len(token.value): |
||||
|
yield Token(lineno, 'data', token.value[pos:]) |
||||
|
|
||||
|
if paren_stack: |
||||
|
raise TemplateSyntaxError('unclosed gettext expression', |
||||
|
token.lineno, stream.name, |
||||
|
stream.filename) |
@ -0,0 +1,128 @@ |
|||||
|
;;; jinja.el --- Jinja mode highlighting |
||||
|
;; |
||||
|
;; Author: Georg Brandl |
||||
|
;; Copyright: (c) 2009 by the Jinja Team |
||||
|
;; Last modified: 2008-05-22 23:04 by gbr |
||||
|
;; |
||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
|
;; |
||||
|
;;; Commentary: |
||||
|
;; |
||||
|
;; Mostly ripped off django-mode by Lennart Borgman. |
||||
|
;; |
||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
|
;; |
||||
|
;; This program is free software; you can redistribute it and/or |
||||
|
;; modify it under the terms of the GNU General Public License as |
||||
|
;; published by the Free Software Foundation; either version 2, or |
||||
|
;; (at your option) any later version. |
||||
|
;; |
||||
|
;; This program is distributed in the hope that it will be useful, |
||||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
|
;; General Public License for more details. |
||||
|
;; |
||||
|
;; You should have received a copy of the GNU General Public License |
||||
|
;; along with this program; see the file COPYING. If not, write to |
||||
|
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth |
||||
|
;; Floor, Boston, MA 02110-1301, USA. |
||||
|
;; |
||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
|
;; |
||||
|
;;; Code: |
||||
|
|
||||
|
(defconst jinja-font-lock-keywords |
||||
|
(list |
||||
|
; (cons (rx "{% comment %}" (submatch (0+ anything)) |
||||
|
; "{% endcomment %}") (list 1 font-lock-comment-face)) |
||||
|
'("{# ?\\(.*?\\) ?#}" . (1 font-lock-comment-face)) |
||||
|
'("{%-?\\|-?%}\\|{{\\|}}" . font-lock-preprocessor-face) |
||||
|
'("{#\\|#}" . font-lock-comment-delimiter-face) |
||||
|
;; first word in a block is a command |
||||
|
'("{%-?[ \t\n]*\\([a-zA-Z_]+\\)" . (1 font-lock-keyword-face)) |
||||
|
;; variables |
||||
|
'("\\({{ ?\\)\\([^|]*?\\)\\(|.*?\\)? ?}}" . (1 font-lock-variable-name-face)) |
||||
|
;; keywords and builtins |
||||
|
(cons (rx word-start |
||||
|
(or "in" "as" "recursive" "not" "and" "or" "if" "else" |
||||
|
"import" "with" "without" "context") |
||||
|
word-end) |
||||
|
font-lock-keyword-face) |
||||
|
(cons (rx word-start |
||||
|
(or "true" "false" "none" "loop" "self" "super") |
||||
|
word-end) |
||||
|
font-lock-builtin-face) |
||||
|
;; tests |
||||
|
'("\\(is\\)[ \t]*\\(not\\)[ \t]*\\([a-zA-Z_]+\\)" |
||||
|
(1 font-lock-keyword-face) (2 font-lock-keyword-face) |
||||
|
(3 font-lock-function-name-face)) |
||||
|
;; builtin filters |
||||
|
(cons (rx |
||||
|
"|" (* space) |
||||
|
(submatch |
||||
|
(or "abs" "batch" "capitalize" "capture" "center" "count" "default" |
||||
|
"dformat" "dictsort" "e" "escape" "filesizeformat" "first" |
||||
|
"float" "format" "getattribute" "getitem" "groupby" "indent" |
||||
|
"int" "join" "jsonencode" "last" "length" "lower" "markdown" |
||||
|
"pprint" "random" "replace" "reverse" "round" "rst" "slice" |
||||
|
"sort" "string" "striptags" "sum" "textile" "title" "trim" |
||||
|
"truncate" "upper" "urlencode" "urlize" "wordcount" "wordwrap" |
||||
|
"xmlattr"))) |
||||
|
(list 1 font-lock-builtin-face)) |
||||
|
) |
||||
|
"Minimal highlighting expressions for Jinja mode") |
||||
|
|
||||
|
(define-derived-mode jinja-mode nil "Jinja" |
||||
|
"Simple Jinja mode for use with `mumamo-mode'. |
||||
|
This mode only provides syntax highlighting." |
||||
|
;;(set (make-local-variable 'comment-start) "{#") |
||||
|
;;(set (make-local-variable 'comment-end) "#}") |
||||
|
(setq font-lock-defaults '(jinja-font-lock-keywords))) |
||||
|
|
||||
|
;; mumamo stuff |
||||
|
(when (require 'mumamo nil t) |
||||
|
|
||||
|
(defun mumamo-chunk-jinja3 (pos max) |
||||
|
"Find {# ... #}" |
||||
|
(mumamo-quick-chunk-forward pos max "{#" "#}" 'borders 'jinja-mode)) |
||||
|
|
||||
|
(defun mumamo-chunk-jinja2 (pos max) |
||||
|
"Find {{ ... }}" |
||||
|
(mumamo-quick-chunk-forward pos max "{{" "}}" 'borders 'jinja-mode)) |
||||
|
|
||||
|
(defun mumamo-chunk-jinja (pos max) |
||||
|
"Find {% ... %}" |
||||
|
(mumamo-quick-chunk-forward pos max "{%" "%}" 'borders 'jinja-mode)) |
||||
|
|
||||
|
;;;###autoload |
||||
|
(define-mumamo-multi-major-mode jinja-html-mumamo |
||||
|
"Turn on multiple major modes for Jinja with main mode `html-mode'. |
||||
|
This also covers inlined style and javascript." |
||||
|
("Jinja HTML Family" html-mode |
||||
|
(mumamo-chunk-jinja |
||||
|
mumamo-chunk-jinja2 |
||||
|
mumamo-chunk-jinja3 |
||||
|
mumamo-chunk-inlined-style |
||||
|
mumamo-chunk-inlined-script |
||||
|
mumamo-chunk-style= |
||||
|
mumamo-chunk-onjs= |
||||
|
))) |
||||
|
|
||||
|
;;;###autoload |
||||
|
(define-mumamo-multi-major-mode jinja-nxhtml-mumamo |
||||
|
"Turn on multiple major modes for Jinja with main mode `nxhtml-mode'. |
||||
|
This also covers inlined style and javascript." |
||||
|
("Jinja nXhtml Family" nxhtml-mode |
||||
|
(mumamo-chunk-jinja |
||||
|
mumamo-chunk-jinja2 |
||||
|
mumamo-chunk-jinja3 |
||||
|
mumamo-chunk-inlined-style |
||||
|
mumamo-chunk-inlined-script |
||||
|
mumamo-chunk-style= |
||||
|
mumamo-chunk-onjs= |
||||
|
))) |
||||
|
) |
||||
|
|
||||
|
(provide 'jinja) |
||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
|
;;; jinja.el ends here |
@ -0,0 +1,70 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2 |
||||
|
~~~~~~ |
||||
|
|
||||
|
Jinja2 is a template engine written in pure Python. It provides a |
||||
|
Django inspired non-XML syntax but supports inline expressions and |
||||
|
an optional sandboxed environment. |
||||
|
|
||||
|
Nutshell |
||||
|
-------- |
||||
|
|
||||
|
Here a small example of a Jinja2 template:: |
||||
|
|
||||
|
{% extends 'base.html' %} |
||||
|
{% block title %}Memberlist{% endblock %} |
||||
|
{% block content %} |
||||
|
<ul> |
||||
|
{% for user in users %} |
||||
|
<li><a href="{{ user.url }}">{{ user.username }}</a></li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
{% endblock %} |
||||
|
|
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
__docformat__ = 'restructuredtext en' |
||||
|
__version__ = '2.9.dev' |
||||
|
|
||||
|
# high level interface |
||||
|
from jinja2.environment import Environment, Template |
||||
|
|
||||
|
# loaders |
||||
|
from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \ |
||||
|
DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \ |
||||
|
ModuleLoader |
||||
|
|
||||
|
# bytecode caches |
||||
|
from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \ |
||||
|
MemcachedBytecodeCache |
||||
|
|
||||
|
# undefined types |
||||
|
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \ |
||||
|
make_logging_undefined |
||||
|
|
||||
|
# exceptions |
||||
|
from jinja2.exceptions import TemplateError, UndefinedError, \ |
||||
|
TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \ |
||||
|
TemplateAssertionError |
||||
|
|
||||
|
# decorators and public utilities |
||||
|
from jinja2.filters import environmentfilter, contextfilter, \ |
||||
|
evalcontextfilter |
||||
|
from jinja2.utils import Markup, escape, clear_caches, \ |
||||
|
environmentfunction, evalcontextfunction, contextfunction, \ |
||||
|
is_undefined |
||||
|
|
||||
|
__all__ = [ |
||||
|
'Environment', 'Template', 'BaseLoader', 'FileSystemLoader', |
||||
|
'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader', |
||||
|
'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache', |
||||
|
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined', |
||||
|
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound', |
||||
|
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError', |
||||
|
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape', |
||||
|
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined', |
||||
|
'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined', |
||||
|
] |
@ -0,0 +1,102 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2._compat |
||||
|
~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Some py2/py3 compatibility support based on a stripped down |
||||
|
version of six so we don't have to depend on a specific version |
||||
|
of it. |
||||
|
|
||||
|
:copyright: Copyright 2013 by the Jinja team, see AUTHORS. |
||||
|
:license: BSD, see LICENSE for details. |
||||
|
""" |
||||
|
import sys |
||||
|
|
||||
|
PY2 = sys.version_info[0] == 2 |
||||
|
PYPY = hasattr(sys, 'pypy_translation_info') |
||||
|
_identity = lambda x: x |
||||
|
|
||||
|
|
||||
|
if not PY2: |
||||
|
unichr = chr |
||||
|
range_type = range |
||||
|
text_type = str |
||||
|
string_types = (str,) |
||||
|
integer_types = (int,) |
||||
|
|
||||
|
iterkeys = lambda d: iter(d.keys()) |
||||
|
itervalues = lambda d: iter(d.values()) |
||||
|
iteritems = lambda d: iter(d.items()) |
||||
|
|
||||
|
import pickle |
||||
|
from io import BytesIO, StringIO |
||||
|
NativeStringIO = StringIO |
||||
|
|
||||
|
def reraise(tp, value, tb=None): |
||||
|
if value.__traceback__ is not tb: |
||||
|
raise value.with_traceback(tb) |
||||
|
raise value |
||||
|
|
||||
|
ifilter = filter |
||||
|
imap = map |
||||
|
izip = zip |
||||
|
intern = sys.intern |
||||
|
|
||||
|
implements_iterator = _identity |
||||
|
implements_to_string = _identity |
||||
|
encode_filename = _identity |
||||
|
get_next = lambda x: x.__next__ |
||||
|
|
||||
|
else: |
||||
|
unichr = unichr |
||||
|
text_type = unicode |
||||
|
range_type = xrange |
||||
|
string_types = (str, unicode) |
||||
|
integer_types = (int, long) |
||||
|
|
||||
|
iterkeys = lambda d: d.iterkeys() |
||||
|
itervalues = lambda d: d.itervalues() |
||||
|
iteritems = lambda d: d.iteritems() |
||||
|
|
||||
|
import cPickle as pickle |
||||
|
from cStringIO import StringIO as BytesIO, StringIO |
||||
|
NativeStringIO = BytesIO |
||||
|
|
||||
|
exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') |
||||
|
|
||||
|
from itertools import imap, izip, ifilter |
||||
|
intern = intern |
||||
|
|
||||
|
def implements_iterator(cls): |
||||
|
cls.next = cls.__next__ |
||||
|
del cls.__next__ |
||||
|
return cls |
||||
|
|
||||
|
def implements_to_string(cls): |
||||
|
cls.__unicode__ = cls.__str__ |
||||
|
cls.__str__ = lambda x: x.__unicode__().encode('utf-8') |
||||
|
return cls |
||||
|
|
||||
|
get_next = lambda x: x.next |
||||
|
|
||||
|
def encode_filename(filename): |
||||
|
if isinstance(filename, unicode): |
||||
|
return filename.encode('utf-8') |
||||
|
return filename |
||||
|
|
||||
|
|
||||
|
def with_metaclass(meta, *bases): |
||||
|
"""Create a base class with a metaclass.""" |
||||
|
# This requires a bit of explanation: the basic idea is to make a |
||||
|
# dummy metaclass for one level of class instantiation that replaces |
||||
|
# itself with the actual metaclass. |
||||
|
class metaclass(type): |
||||
|
def __new__(cls, name, this_bases, d): |
||||
|
return meta(name, bases, d) |
||||
|
return type.__new__(metaclass, 'temporary_class', (), {}) |
||||
|
|
||||
|
|
||||
|
try: |
||||
|
from urllib.parse import quote_from_bytes as url_quote |
||||
|
except ImportError: |
||||
|
from urllib import quote as url_quote |
File diff suppressed because one or more lines are too long
@ -0,0 +1,362 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.bccache |
||||
|
~~~~~~~~~~~~~~ |
||||
|
|
||||
|
This module implements the bytecode cache system Jinja is optionally |
||||
|
using. This is useful if you have very complex template situations and |
||||
|
the compiliation of all those templates slow down your application too |
||||
|
much. |
||||
|
|
||||
|
Situations where this is useful are often forking web applications that |
||||
|
are initialized on the first request. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
from os import path, listdir |
||||
|
import os |
||||
|
import sys |
||||
|
import stat |
||||
|
import errno |
||||
|
import marshal |
||||
|
import tempfile |
||||
|
import fnmatch |
||||
|
from hashlib import sha1 |
||||
|
from jinja2.utils import open_if_exists |
||||
|
from jinja2._compat import BytesIO, pickle, PY2, text_type |
||||
|
|
||||
|
|
||||
|
# marshal works better on 3.x, one hack less required |
||||
|
if not PY2: |
||||
|
marshal_dump = marshal.dump |
||||
|
marshal_load = marshal.load |
||||
|
else: |
||||
|
|
||||
|
def marshal_dump(code, f): |
||||
|
if isinstance(f, file): |
||||
|
marshal.dump(code, f) |
||||
|
else: |
||||
|
f.write(marshal.dumps(code)) |
||||
|
|
||||
|
def marshal_load(f): |
||||
|
if isinstance(f, file): |
||||
|
return marshal.load(f) |
||||
|
return marshal.loads(f.read()) |
||||
|
|
||||
|
|
||||
|
bc_version = 2 |
||||
|
|
||||
|
# magic version used to only change with new jinja versions. With 2.6 |
||||
|
# we change this to also take Python version changes into account. The |
||||
|
# reason for this is that Python tends to segfault if fed earlier bytecode |
||||
|
# versions because someone thought it would be a good idea to reuse opcodes |
||||
|
# or make Python incompatible with earlier versions. |
||||
|
bc_magic = 'j2'.encode('ascii') + \ |
||||
|
pickle.dumps(bc_version, 2) + \ |
||||
|
pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1]) |
||||
|
|
||||
|
|
||||
|
class Bucket(object): |
||||
|
"""Buckets are used to store the bytecode for one template. It's created |
||||
|
and initialized by the bytecode cache and passed to the loading functions. |
||||
|
|
||||
|
The buckets get an internal checksum from the cache assigned and use this |
||||
|
to automatically reject outdated cache material. Individual bytecode |
||||
|
cache subclasses don't have to care about cache invalidation. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, environment, key, checksum): |
||||
|
self.environment = environment |
||||
|
self.key = key |
||||
|
self.checksum = checksum |
||||
|
self.reset() |
||||
|
|
||||
|
def reset(self): |
||||
|
"""Resets the bucket (unloads the bytecode).""" |
||||
|
self.code = None |
||||
|
|
||||
|
def load_bytecode(self, f): |
||||
|
"""Loads bytecode from a file or file like object.""" |
||||
|
# make sure the magic header is correct |
||||
|
magic = f.read(len(bc_magic)) |
||||
|
if magic != bc_magic: |
||||
|
self.reset() |
||||
|
return |
||||
|
# the source code of the file changed, we need to reload |
||||
|
checksum = pickle.load(f) |
||||
|
if self.checksum != checksum: |
||||
|
self.reset() |
||||
|
return |
||||
|
# if marshal_load fails then we need to reload |
||||
|
try: |
||||
|
self.code = marshal_load(f) |
||||
|
except (EOFError, ValueError, TypeError): |
||||
|
self.reset() |
||||
|
return |
||||
|
|
||||
|
def write_bytecode(self, f): |
||||
|
"""Dump the bytecode into the file or file like object passed.""" |
||||
|
if self.code is None: |
||||
|
raise TypeError('can\'t write empty bucket') |
||||
|
f.write(bc_magic) |
||||
|
pickle.dump(self.checksum, f, 2) |
||||
|
marshal_dump(self.code, f) |
||||
|
|
||||
|
def bytecode_from_string(self, string): |
||||
|
"""Load bytecode from a string.""" |
||||
|
self.load_bytecode(BytesIO(string)) |
||||
|
|
||||
|
def bytecode_to_string(self): |
||||
|
"""Return the bytecode as string.""" |
||||
|
out = BytesIO() |
||||
|
self.write_bytecode(out) |
||||
|
return out.getvalue() |
||||
|
|
||||
|
|
||||
|
class BytecodeCache(object): |
||||
|
"""To implement your own bytecode cache you have to subclass this class |
||||
|
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of |
||||
|
these methods are passed a :class:`~jinja2.bccache.Bucket`. |
||||
|
|
||||
|
A very basic bytecode cache that saves the bytecode on the file system:: |
||||
|
|
||||
|
from os import path |
||||
|
|
||||
|
class MyCache(BytecodeCache): |
||||
|
|
||||
|
def __init__(self, directory): |
||||
|
self.directory = directory |
||||
|
|
||||
|
def load_bytecode(self, bucket): |
||||
|
filename = path.join(self.directory, bucket.key) |
||||
|
if path.exists(filename): |
||||
|
with open(filename, 'rb') as f: |
||||
|
bucket.load_bytecode(f) |
||||
|
|
||||
|
def dump_bytecode(self, bucket): |
||||
|
filename = path.join(self.directory, bucket.key) |
||||
|
with open(filename, 'wb') as f: |
||||
|
bucket.write_bytecode(f) |
||||
|
|
||||
|
A more advanced version of a filesystem based bytecode cache is part of |
||||
|
Jinja2. |
||||
|
""" |
||||
|
|
||||
|
def load_bytecode(self, bucket): |
||||
|
"""Subclasses have to override this method to load bytecode into a |
||||
|
bucket. If they are not able to find code in the cache for the |
||||
|
bucket, it must not do anything. |
||||
|
""" |
||||
|
raise NotImplementedError() |
||||
|
|
||||
|
def dump_bytecode(self, bucket): |
||||
|
"""Subclasses have to override this method to write the bytecode |
||||
|
from a bucket back to the cache. If it unable to do so it must not |
||||
|
fail silently but raise an exception. |
||||
|
""" |
||||
|
raise NotImplementedError() |
||||
|
|
||||
|
def clear(self): |
||||
|
"""Clears the cache. This method is not used by Jinja2 but should be |
||||
|
implemented to allow applications to clear the bytecode cache used |
||||
|
by a particular environment. |
||||
|
""" |
||||
|
|
||||
|
def get_cache_key(self, name, filename=None): |
||||
|
"""Returns the unique hash key for this template name.""" |
||||
|
hash = sha1(name.encode('utf-8')) |
||||
|
if filename is not None: |
||||
|
filename = '|' + filename |
||||
|
if isinstance(filename, text_type): |
||||
|
filename = filename.encode('utf-8') |
||||
|
hash.update(filename) |
||||
|
return hash.hexdigest() |
||||
|
|
||||
|
def get_source_checksum(self, source): |
||||
|
"""Returns a checksum for the source.""" |
||||
|
return sha1(source.encode('utf-8')).hexdigest() |
||||
|
|
||||
|
def get_bucket(self, environment, name, filename, source): |
||||
|
"""Return a cache bucket for the given template. All arguments are |
||||
|
mandatory but filename may be `None`. |
||||
|
""" |
||||
|
key = self.get_cache_key(name, filename) |
||||
|
checksum = self.get_source_checksum(source) |
||||
|
bucket = Bucket(environment, key, checksum) |
||||
|
self.load_bytecode(bucket) |
||||
|
return bucket |
||||
|
|
||||
|
def set_bucket(self, bucket): |
||||
|
"""Put the bucket into the cache.""" |
||||
|
self.dump_bytecode(bucket) |
||||
|
|
||||
|
|
||||
|
class FileSystemBytecodeCache(BytecodeCache): |
||||
|
"""A bytecode cache that stores bytecode on the filesystem. It accepts |
||||
|
two arguments: The directory where the cache items are stored and a |
||||
|
pattern string that is used to build the filename. |
||||
|
|
||||
|
If no directory is specified a default cache directory is selected. On |
||||
|
Windows the user's temp directory is used, on UNIX systems a directory |
||||
|
is created for the user in the system temp directory. |
||||
|
|
||||
|
The pattern can be used to have multiple separate caches operate on the |
||||
|
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s`` |
||||
|
is replaced with the cache key. |
||||
|
|
||||
|
>>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache') |
||||
|
|
||||
|
This bytecode cache supports clearing of the cache using the clear method. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, directory=None, pattern='__jinja2_%s.cache'): |
||||
|
if directory is None: |
||||
|
directory = self._get_default_cache_dir() |
||||
|
self.directory = directory |
||||
|
self.pattern = pattern |
||||
|
|
||||
|
def _get_default_cache_dir(self): |
||||
|
def _unsafe_dir(): |
||||
|
raise RuntimeError('Cannot determine safe temp directory. You ' |
||||
|
'need to explicitly provide one.') |
||||
|
|
||||
|
tmpdir = tempfile.gettempdir() |
||||
|
|
||||
|
# On windows the temporary directory is used specific unless |
||||
|
# explicitly forced otherwise. We can just use that. |
||||
|
if os.name == 'nt': |
||||
|
return tmpdir |
||||
|
if not hasattr(os, 'getuid'): |
||||
|
_unsafe_dir() |
||||
|
|
||||
|
dirname = '_jinja2-cache-%d' % os.getuid() |
||||
|
actual_dir = os.path.join(tmpdir, dirname) |
||||
|
|
||||
|
try: |
||||
|
os.mkdir(actual_dir, stat.S_IRWXU) |
||||
|
except OSError as e: |
||||
|
if e.errno != errno.EEXIST: |
||||
|
raise |
||||
|
try: |
||||
|
os.chmod(actual_dir, stat.S_IRWXU) |
||||
|
actual_dir_stat = os.lstat(actual_dir) |
||||
|
if actual_dir_stat.st_uid != os.getuid() \ |
||||
|
or not stat.S_ISDIR(actual_dir_stat.st_mode) \ |
||||
|
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU: |
||||
|
_unsafe_dir() |
||||
|
except OSError as e: |
||||
|
if e.errno != errno.EEXIST: |
||||
|
raise |
||||
|
|
||||
|
actual_dir_stat = os.lstat(actual_dir) |
||||
|
if actual_dir_stat.st_uid != os.getuid() \ |
||||
|
or not stat.S_ISDIR(actual_dir_stat.st_mode) \ |
||||
|
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU: |
||||
|
_unsafe_dir() |
||||
|
|
||||
|
return actual_dir |
||||
|
|
||||
|
def _get_cache_filename(self, bucket): |
||||
|
return path.join(self.directory, self.pattern % bucket.key) |
||||
|
|
||||
|
def load_bytecode(self, bucket): |
||||
|
f = open_if_exists(self._get_cache_filename(bucket), 'rb') |
||||
|
if f is not None: |
||||
|
try: |
||||
|
bucket.load_bytecode(f) |
||||
|
finally: |
||||
|
f.close() |
||||
|
|
||||
|
def dump_bytecode(self, bucket): |
||||
|
f = open(self._get_cache_filename(bucket), 'wb') |
||||
|
try: |
||||
|
bucket.write_bytecode(f) |
||||
|
finally: |
||||
|
f.close() |
||||
|
|
||||
|
def clear(self): |
||||
|
# imported lazily here because google app-engine doesn't support |
||||
|
# write access on the file system and the function does not exist |
||||
|
# normally. |
||||
|
from os import remove |
||||
|
files = fnmatch.filter(listdir(self.directory), self.pattern % '*') |
||||
|
for filename in files: |
||||
|
try: |
||||
|
remove(path.join(self.directory, filename)) |
||||
|
except OSError: |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
class MemcachedBytecodeCache(BytecodeCache): |
||||
|
"""This class implements a bytecode cache that uses a memcache cache for |
||||
|
storing the information. It does not enforce a specific memcache library |
||||
|
(tummy's memcache or cmemcache) but will accept any class that provides |
||||
|
the minimal interface required. |
||||
|
|
||||
|
Libraries compatible with this class: |
||||
|
|
||||
|
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache |
||||
|
- `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_ |
||||
|
- `cmemcache <http://gijsbert.org/cmemcache/>`_ |
||||
|
|
||||
|
(Unfortunately the django cache interface is not compatible because it |
||||
|
does not support storing binary data, only unicode. You can however pass |
||||
|
the underlying cache client to the bytecode cache which is available |
||||
|
as `django.core.cache.cache._client`.) |
||||
|
|
||||
|
The minimal interface for the client passed to the constructor is this: |
||||
|
|
||||
|
.. class:: MinimalClientInterface |
||||
|
|
||||
|
.. method:: set(key, value[, timeout]) |
||||
|
|
||||
|
Stores the bytecode in the cache. `value` is a string and |
||||
|
`timeout` the timeout of the key. If timeout is not provided |
||||
|
a default timeout or no timeout should be assumed, if it's |
||||
|
provided it's an integer with the number of seconds the cache |
||||
|
item should exist. |
||||
|
|
||||
|
.. method:: get(key) |
||||
|
|
||||
|
Returns the value for the cache key. If the item does not |
||||
|
exist in the cache the return value must be `None`. |
||||
|
|
||||
|
The other arguments to the constructor are the prefix for all keys that |
||||
|
is added before the actual cache key and the timeout for the bytecode in |
||||
|
the cache system. We recommend a high (or no) timeout. |
||||
|
|
||||
|
This bytecode cache does not support clearing of used items in the cache. |
||||
|
The clear method is a no-operation function. |
||||
|
|
||||
|
.. versionadded:: 2.7 |
||||
|
Added support for ignoring memcache errors through the |
||||
|
`ignore_memcache_errors` parameter. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, client, prefix='jinja2/bytecode/', timeout=None, |
||||
|
ignore_memcache_errors=True): |
||||
|
self.client = client |
||||
|
self.prefix = prefix |
||||
|
self.timeout = timeout |
||||
|
self.ignore_memcache_errors = ignore_memcache_errors |
||||
|
|
||||
|
def load_bytecode(self, bucket): |
||||
|
try: |
||||
|
code = self.client.get(self.prefix + bucket.key) |
||||
|
except Exception: |
||||
|
if not self.ignore_memcache_errors: |
||||
|
raise |
||||
|
code = None |
||||
|
if code is not None: |
||||
|
bucket.bytecode_from_string(code) |
||||
|
|
||||
|
def dump_bytecode(self, bucket): |
||||
|
args = (self.prefix + bucket.key, bucket.bytecode_to_string()) |
||||
|
if self.timeout is not None: |
||||
|
args += (self.timeout,) |
||||
|
try: |
||||
|
self.client.set(*args) |
||||
|
except Exception: |
||||
|
if not self.ignore_memcache_errors: |
||||
|
raise |
File diff suppressed because it is too large
@ -0,0 +1,32 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja.constants |
||||
|
~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Various constants. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
|
||||
|
|
||||
|
#: list of lorem ipsum words used by the lipsum() helper function |
||||
|
LOREM_IPSUM_WORDS = u'''\ |
||||
|
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at |
||||
|
auctor augue bibendum blandit class commodo condimentum congue consectetuer |
||||
|
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus |
||||
|
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend |
||||
|
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames |
||||
|
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac |
||||
|
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum |
||||
|
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem |
||||
|
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie |
||||
|
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non |
||||
|
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque |
||||
|
penatibus per pharetra phasellus placerat platea porta porttitor posuere |
||||
|
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus |
||||
|
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit |
||||
|
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor |
||||
|
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices |
||||
|
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus |
||||
|
viverra volutpat vulputate''' |
@ -0,0 +1,350 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.debug |
||||
|
~~~~~~~~~~~~ |
||||
|
|
||||
|
Implements the debug interface for Jinja. This module does some pretty |
||||
|
ugly stuff with the Python traceback system in order to achieve tracebacks |
||||
|
with correct line numbers, locals and contents. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
import sys |
||||
|
import traceback |
||||
|
from types import TracebackType, CodeType |
||||
|
from jinja2.utils import missing, internal_code |
||||
|
from jinja2.exceptions import TemplateSyntaxError |
||||
|
from jinja2._compat import iteritems, reraise, PY2 |
||||
|
|
||||
|
# on pypy we can take advantage of transparent proxies |
||||
|
try: |
||||
|
from __pypy__ import tproxy |
||||
|
except ImportError: |
||||
|
tproxy = None |
||||
|
|
||||
|
|
||||
|
# how does the raise helper look like? |
||||
|
try: |
||||
|
exec("raise TypeError, 'foo'") |
||||
|
except SyntaxError: |
||||
|
raise_helper = 'raise __jinja_exception__[1]' |
||||
|
except TypeError: |
||||
|
raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]' |
||||
|
|
||||
|
|
||||
|
class TracebackFrameProxy(object): |
||||
|
"""Proxies a traceback frame.""" |
||||
|
|
||||
|
def __init__(self, tb): |
||||
|
self.tb = tb |
||||
|
self._tb_next = None |
||||
|
|
||||
|
@property |
||||
|
def tb_next(self): |
||||
|
return self._tb_next |
||||
|
|
||||
|
def set_next(self, next): |
||||
|
if tb_set_next is not None: |
||||
|
try: |
||||
|
tb_set_next(self.tb, next and next.tb or None) |
||||
|
except Exception: |
||||
|
# this function can fail due to all the hackery it does |
||||
|
# on various python implementations. We just catch errors |
||||
|
# down and ignore them if necessary. |
||||
|
pass |
||||
|
self._tb_next = next |
||||
|
|
||||
|
@property |
||||
|
def is_jinja_frame(self): |
||||
|
return '__jinja_template__' in self.tb.tb_frame.f_globals |
||||
|
|
||||
|
def __getattr__(self, name): |
||||
|
return getattr(self.tb, name) |
||||
|
|
||||
|
|
||||
|
def make_frame_proxy(frame): |
||||
|
proxy = TracebackFrameProxy(frame) |
||||
|
if tproxy is None: |
||||
|
return proxy |
||||
|
def operation_handler(operation, *args, **kwargs): |
||||
|
if operation in ('__getattribute__', '__getattr__'): |
||||
|
return getattr(proxy, args[0]) |
||||
|
elif operation == '__setattr__': |
||||
|
proxy.__setattr__(*args, **kwargs) |
||||
|
else: |
||||
|
return getattr(proxy, operation)(*args, **kwargs) |
||||
|
return tproxy(TracebackType, operation_handler) |
||||
|
|
||||
|
|
||||
|
class ProcessedTraceback(object): |
||||
|
"""Holds a Jinja preprocessed traceback for printing or reraising.""" |
||||
|
|
||||
|
def __init__(self, exc_type, exc_value, frames): |
||||
|
assert frames, 'no frames for this traceback?' |
||||
|
self.exc_type = exc_type |
||||
|
self.exc_value = exc_value |
||||
|
self.frames = frames |
||||
|
|
||||
|
# newly concatenate the frames (which are proxies) |
||||
|
prev_tb = None |
||||
|
for tb in self.frames: |
||||
|
if prev_tb is not None: |
||||
|
prev_tb.set_next(tb) |
||||
|
prev_tb = tb |
||||
|
prev_tb.set_next(None) |
||||
|
|
||||
|
def render_as_text(self, limit=None): |
||||
|
"""Return a string with the traceback.""" |
||||
|
lines = traceback.format_exception(self.exc_type, self.exc_value, |
||||
|
self.frames[0], limit=limit) |
||||
|
return ''.join(lines).rstrip() |
||||
|
|
||||
|
def render_as_html(self, full=False): |
||||
|
"""Return a unicode string with the traceback as rendered HTML.""" |
||||
|
from jinja2.debugrenderer import render_traceback |
||||
|
return u'%s\n\n<!--\n%s\n-->' % ( |
||||
|
render_traceback(self, full=full), |
||||
|
self.render_as_text().decode('utf-8', 'replace') |
||||
|
) |
||||
|
|
||||
|
@property |
||||
|
def is_template_syntax_error(self): |
||||
|
"""`True` if this is a template syntax error.""" |
||||
|
return isinstance(self.exc_value, TemplateSyntaxError) |
||||
|
|
||||
|
@property |
||||
|
def exc_info(self): |
||||
|
"""Exception info tuple with a proxy around the frame objects.""" |
||||
|
return self.exc_type, self.exc_value, self.frames[0] |
||||
|
|
||||
|
@property |
||||
|
def standard_exc_info(self): |
||||
|
"""Standard python exc_info for re-raising""" |
||||
|
tb = self.frames[0] |
||||
|
# the frame will be an actual traceback (or transparent proxy) if |
||||
|
# we are on pypy or a python implementation with support for tproxy |
||||
|
if type(tb) is not TracebackType: |
||||
|
tb = tb.tb |
||||
|
return self.exc_type, self.exc_value, tb |
||||
|
|
||||
|
|
||||
|
def make_traceback(exc_info, source_hint=None): |
||||
|
"""Creates a processed traceback object from the exc_info.""" |
||||
|
exc_type, exc_value, tb = exc_info |
||||
|
if isinstance(exc_value, TemplateSyntaxError): |
||||
|
exc_info = translate_syntax_error(exc_value, source_hint) |
||||
|
initial_skip = 0 |
||||
|
else: |
||||
|
initial_skip = 1 |
||||
|
return translate_exception(exc_info, initial_skip) |
||||
|
|
||||
|
|
||||
|
def translate_syntax_error(error, source=None): |
||||
|
"""Rewrites a syntax error to please traceback systems.""" |
||||
|
error.source = source |
||||
|
error.translated = True |
||||
|
exc_info = (error.__class__, error, None) |
||||
|
filename = error.filename |
||||
|
if filename is None: |
||||
|
filename = '<unknown>' |
||||
|
return fake_exc_info(exc_info, filename, error.lineno) |
||||
|
|
||||
|
|
||||
|
def translate_exception(exc_info, initial_skip=0): |
||||
|
"""If passed an exc_info it will automatically rewrite the exceptions |
||||
|
all the way down to the correct line numbers and frames. |
||||
|
""" |
||||
|
tb = exc_info[2] |
||||
|
frames = [] |
||||
|
|
||||
|
# skip some internal frames if wanted |
||||
|
for x in range(initial_skip): |
||||
|
if tb is not None: |
||||
|
tb = tb.tb_next |
||||
|
initial_tb = tb |
||||
|
|
||||
|
while tb is not None: |
||||
|
# skip frames decorated with @internalcode. These are internal |
||||
|
# calls we can't avoid and that are useless in template debugging |
||||
|
# output. |
||||
|
if tb.tb_frame.f_code in internal_code: |
||||
|
tb = tb.tb_next |
||||
|
continue |
||||
|
|
||||
|
# save a reference to the next frame if we override the current |
||||
|
# one with a faked one. |
||||
|
next = tb.tb_next |
||||
|
|
||||
|
# fake template exceptions |
||||
|
template = tb.tb_frame.f_globals.get('__jinja_template__') |
||||
|
if template is not None: |
||||
|
lineno = template.get_corresponding_lineno(tb.tb_lineno) |
||||
|
tb = fake_exc_info(exc_info[:2] + (tb,), template.filename, |
||||
|
lineno)[2] |
||||
|
|
||||
|
frames.append(make_frame_proxy(tb)) |
||||
|
tb = next |
||||
|
|
||||
|
# if we don't have any exceptions in the frames left, we have to |
||||
|
# reraise it unchanged. |
||||
|
# XXX: can we backup here? when could this happen? |
||||
|
if not frames: |
||||
|
reraise(exc_info[0], exc_info[1], exc_info[2]) |
||||
|
|
||||
|
return ProcessedTraceback(exc_info[0], exc_info[1], frames) |
||||
|
|
||||
|
|
||||
|
def fake_exc_info(exc_info, filename, lineno): |
||||
|
"""Helper for `translate_exception`.""" |
||||
|
exc_type, exc_value, tb = exc_info |
||||
|
|
||||
|
# figure the real context out |
||||
|
if tb is not None: |
||||
|
real_locals = tb.tb_frame.f_locals.copy() |
||||
|
ctx = real_locals.get('context') |
||||
|
if ctx: |
||||
|
locals = ctx.get_all() |
||||
|
else: |
||||
|
locals = {} |
||||
|
for name, value in iteritems(real_locals): |
||||
|
if name.startswith('l_') and value is not missing: |
||||
|
locals[name[2:]] = value |
||||
|
|
||||
|
# if there is a local called __jinja_exception__, we get |
||||
|
# rid of it to not break the debug functionality. |
||||
|
locals.pop('__jinja_exception__', None) |
||||
|
else: |
||||
|
locals = {} |
||||
|
|
||||
|
# assamble fake globals we need |
||||
|
globals = { |
||||
|
'__name__': filename, |
||||
|
'__file__': filename, |
||||
|
'__jinja_exception__': exc_info[:2], |
||||
|
|
||||
|
# we don't want to keep the reference to the template around |
||||
|
# to not cause circular dependencies, but we mark it as Jinja |
||||
|
# frame for the ProcessedTraceback |
||||
|
'__jinja_template__': None |
||||
|
} |
||||
|
|
||||
|
# and fake the exception |
||||
|
code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec') |
||||
|
|
||||
|
# if it's possible, change the name of the code. This won't work |
||||
|
# on some python environments such as google appengine |
||||
|
try: |
||||
|
if tb is None: |
||||
|
location = 'template' |
||||
|
else: |
||||
|
function = tb.tb_frame.f_code.co_name |
||||
|
if function == 'root': |
||||
|
location = 'top-level template code' |
||||
|
elif function.startswith('block_'): |
||||
|
location = 'block "%s"' % function[6:] |
||||
|
else: |
||||
|
location = 'template' |
||||
|
|
||||
|
if PY2: |
||||
|
code = CodeType(0, code.co_nlocals, code.co_stacksize, |
||||
|
code.co_flags, code.co_code, code.co_consts, |
||||
|
code.co_names, code.co_varnames, filename, |
||||
|
location, code.co_firstlineno, |
||||
|
code.co_lnotab, (), ()) |
||||
|
else: |
||||
|
code = CodeType(0, code.co_kwonlyargcount, |
||||
|
code.co_nlocals, code.co_stacksize, |
||||
|
code.co_flags, code.co_code, code.co_consts, |
||||
|
code.co_names, code.co_varnames, filename, |
||||
|
location, code.co_firstlineno, |
||||
|
code.co_lnotab, (), ()) |
||||
|
except Exception as e: |
||||
|
pass |
||||
|
|
||||
|
# execute the code and catch the new traceback |
||||
|
try: |
||||
|
exec(code, globals, locals) |
||||
|
except: |
||||
|
exc_info = sys.exc_info() |
||||
|
new_tb = exc_info[2].tb_next |
||||
|
|
||||
|
# return without this frame |
||||
|
return exc_info[:2] + (new_tb,) |
||||
|
|
||||
|
|
||||
|
def _init_ugly_crap(): |
||||
|
"""This function implements a few ugly things so that we can patch the |
||||
|
traceback objects. The function returned allows resetting `tb_next` on |
||||
|
any python traceback object. Do not attempt to use this on non cpython |
||||
|
interpreters |
||||
|
""" |
||||
|
import ctypes |
||||
|
from types import TracebackType |
||||
|
|
||||
|
if PY2: |
||||
|
# figure out size of _Py_ssize_t for Python 2: |
||||
|
if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'): |
||||
|
_Py_ssize_t = ctypes.c_int64 |
||||
|
else: |
||||
|
_Py_ssize_t = ctypes.c_int |
||||
|
else: |
||||
|
# platform ssize_t on Python 3 |
||||
|
_Py_ssize_t = ctypes.c_ssize_t |
||||
|
|
||||
|
# regular python |
||||
|
class _PyObject(ctypes.Structure): |
||||
|
pass |
||||
|
_PyObject._fields_ = [ |
||||
|
('ob_refcnt', _Py_ssize_t), |
||||
|
('ob_type', ctypes.POINTER(_PyObject)) |
||||
|
] |
||||
|
|
||||
|
# python with trace |
||||
|
if hasattr(sys, 'getobjects'): |
||||
|
class _PyObject(ctypes.Structure): |
||||
|
pass |
||||
|
_PyObject._fields_ = [ |
||||
|
('_ob_next', ctypes.POINTER(_PyObject)), |
||||
|
('_ob_prev', ctypes.POINTER(_PyObject)), |
||||
|
('ob_refcnt', _Py_ssize_t), |
||||
|
('ob_type', ctypes.POINTER(_PyObject)) |
||||
|
] |
||||
|
|
||||
|
class _Traceback(_PyObject): |
||||
|
pass |
||||
|
_Traceback._fields_ = [ |
||||
|
('tb_next', ctypes.POINTER(_Traceback)), |
||||
|
('tb_frame', ctypes.POINTER(_PyObject)), |
||||
|
('tb_lasti', ctypes.c_int), |
||||
|
('tb_lineno', ctypes.c_int) |
||||
|
] |
||||
|
|
||||
|
def tb_set_next(tb, next): |
||||
|
"""Set the tb_next attribute of a traceback object.""" |
||||
|
if not (isinstance(tb, TracebackType) and |
||||
|
(next is None or isinstance(next, TracebackType))): |
||||
|
raise TypeError('tb_set_next arguments must be traceback objects') |
||||
|
obj = _Traceback.from_address(id(tb)) |
||||
|
if tb.tb_next is not None: |
||||
|
old = _Traceback.from_address(id(tb.tb_next)) |
||||
|
old.ob_refcnt -= 1 |
||||
|
if next is None: |
||||
|
obj.tb_next = ctypes.POINTER(_Traceback)() |
||||
|
else: |
||||
|
next = _Traceback.from_address(id(next)) |
||||
|
next.ob_refcnt += 1 |
||||
|
obj.tb_next = ctypes.pointer(next) |
||||
|
|
||||
|
return tb_set_next |
||||
|
|
||||
|
|
||||
|
# try to get a tb_set_next implementation if we don't have transparent |
||||
|
# proxies. |
||||
|
tb_set_next = None |
||||
|
if tproxy is None: |
||||
|
try: |
||||
|
tb_set_next = _init_ugly_crap() |
||||
|
except: |
||||
|
pass |
||||
|
del _init_ugly_crap |
@ -0,0 +1,43 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.defaults |
||||
|
~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Jinja default filters and tags. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
from jinja2._compat import range_type |
||||
|
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner |
||||
|
|
||||
|
|
||||
|
# defaults for the parser / lexer |
||||
|
BLOCK_START_STRING = '{%' |
||||
|
BLOCK_END_STRING = '%}' |
||||
|
VARIABLE_START_STRING = '{{' |
||||
|
VARIABLE_END_STRING = '}}' |
||||
|
COMMENT_START_STRING = '{#' |
||||
|
COMMENT_END_STRING = '#}' |
||||
|
LINE_STATEMENT_PREFIX = None |
||||
|
LINE_COMMENT_PREFIX = None |
||||
|
TRIM_BLOCKS = False |
||||
|
LSTRIP_BLOCKS = False |
||||
|
NEWLINE_SEQUENCE = '\n' |
||||
|
KEEP_TRAILING_NEWLINE = False |
||||
|
|
||||
|
|
||||
|
# default filters, tests and namespace |
||||
|
from jinja2.filters import FILTERS as DEFAULT_FILTERS |
||||
|
from jinja2.tests import TESTS as DEFAULT_TESTS |
||||
|
DEFAULT_NAMESPACE = { |
||||
|
'range': range_type, |
||||
|
'dict': dict, |
||||
|
'lipsum': generate_lorem_ipsum, |
||||
|
'cycler': Cycler, |
||||
|
'joiner': Joiner |
||||
|
} |
||||
|
|
||||
|
|
||||
|
# export all constants |
||||
|
__all__ = tuple(x for x in locals().keys() if x.isupper()) |
File diff suppressed because it is too large
@ -0,0 +1,146 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.exceptions |
||||
|
~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Jinja exceptions. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
from jinja2._compat import imap, text_type, PY2, implements_to_string |
||||
|
|
||||
|
|
||||
|
class TemplateError(Exception): |
||||
|
"""Baseclass for all template errors.""" |
||||
|
|
||||
|
if PY2: |
||||
|
def __init__(self, message=None): |
||||
|
if message is not None: |
||||
|
message = text_type(message).encode('utf-8') |
||||
|
Exception.__init__(self, message) |
||||
|
|
||||
|
@property |
||||
|
def message(self): |
||||
|
if self.args: |
||||
|
message = self.args[0] |
||||
|
if message is not None: |
||||
|
return message.decode('utf-8', 'replace') |
||||
|
|
||||
|
def __unicode__(self): |
||||
|
return self.message or u'' |
||||
|
else: |
||||
|
def __init__(self, message=None): |
||||
|
Exception.__init__(self, message) |
||||
|
|
||||
|
@property |
||||
|
def message(self): |
||||
|
if self.args: |
||||
|
message = self.args[0] |
||||
|
if message is not None: |
||||
|
return message |
||||
|
|
||||
|
|
||||
|
@implements_to_string |
||||
|
class TemplateNotFound(IOError, LookupError, TemplateError): |
||||
|
"""Raised if a template does not exist.""" |
||||
|
|
||||
|
# looks weird, but removes the warning descriptor that just |
||||
|
# bogusly warns us about message being deprecated |
||||
|
message = None |
||||
|
|
||||
|
def __init__(self, name, message=None): |
||||
|
IOError.__init__(self) |
||||
|
if message is None: |
||||
|
message = name |
||||
|
self.message = message |
||||
|
self.name = name |
||||
|
self.templates = [name] |
||||
|
|
||||
|
def __str__(self): |
||||
|
return self.message |
||||
|
|
||||
|
|
||||
|
class TemplatesNotFound(TemplateNotFound): |
||||
|
"""Like :class:`TemplateNotFound` but raised if multiple templates |
||||
|
are selected. This is a subclass of :class:`TemplateNotFound` |
||||
|
exception, so just catching the base exception will catch both. |
||||
|
|
||||
|
.. versionadded:: 2.2 |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, names=(), message=None): |
||||
|
if message is None: |
||||
|
message = u'none of the templates given were found: ' + \ |
||||
|
u', '.join(imap(text_type, names)) |
||||
|
TemplateNotFound.__init__(self, names and names[-1] or None, message) |
||||
|
self.templates = list(names) |
||||
|
|
||||
|
|
||||
|
@implements_to_string |
||||
|
class TemplateSyntaxError(TemplateError): |
||||
|
"""Raised to tell the user that there is a problem with the template.""" |
||||
|
|
||||
|
def __init__(self, message, lineno, name=None, filename=None): |
||||
|
TemplateError.__init__(self, message) |
||||
|
self.lineno = lineno |
||||
|
self.name = name |
||||
|
self.filename = filename |
||||
|
self.source = None |
||||
|
|
||||
|
# this is set to True if the debug.translate_syntax_error |
||||
|
# function translated the syntax error into a new traceback |
||||
|
self.translated = False |
||||
|
|
||||
|
def __str__(self): |
||||
|
# for translated errors we only return the message |
||||
|
if self.translated: |
||||
|
return self.message |
||||
|
|
||||
|
# otherwise attach some stuff |
||||
|
location = 'line %d' % self.lineno |
||||
|
name = self.filename or self.name |
||||
|
if name: |
||||
|
location = 'File "%s", %s' % (name, location) |
||||
|
lines = [self.message, ' ' + location] |
||||
|
|
||||
|
# if the source is set, add the line to the output |
||||
|
if self.source is not None: |
||||
|
try: |
||||
|
line = self.source.splitlines()[self.lineno - 1] |
||||
|
except IndexError: |
||||
|
line = None |
||||
|
if line: |
||||
|
lines.append(' ' + line.strip()) |
||||
|
|
||||
|
return u'\n'.join(lines) |
||||
|
|
||||
|
|
||||
|
class TemplateAssertionError(TemplateSyntaxError): |
||||
|
"""Like a template syntax error, but covers cases where something in the |
||||
|
template caused an error at compile time that wasn't necessarily caused |
||||
|
by a syntax error. However it's a direct subclass of |
||||
|
:exc:`TemplateSyntaxError` and has the same attributes. |
||||
|
""" |
||||
|
|
||||
|
|
||||
|
class TemplateRuntimeError(TemplateError): |
||||
|
"""A generic runtime error in the template engine. Under some situations |
||||
|
Jinja may raise this exception. |
||||
|
""" |
||||
|
|
||||
|
|
||||
|
class UndefinedError(TemplateRuntimeError): |
||||
|
"""Raised if a template tries to operate on :class:`Undefined`.""" |
||||
|
|
||||
|
|
||||
|
class SecurityError(TemplateRuntimeError): |
||||
|
"""Raised if a template tries to do something insecure if the |
||||
|
sandbox is enabled. |
||||
|
""" |
||||
|
|
||||
|
|
||||
|
class FilterArgumentError(TemplateRuntimeError): |
||||
|
"""This error is raised if a filter was called with inappropriate |
||||
|
arguments |
||||
|
""" |
@ -0,0 +1,636 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.ext |
||||
|
~~~~~~~~~~ |
||||
|
|
||||
|
Jinja extensions allow to add custom tags similar to the way django custom |
||||
|
tags work. By default two example extensions exist: an i18n and a cache |
||||
|
extension. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
from jinja2 import nodes |
||||
|
from jinja2.defaults import BLOCK_START_STRING, \ |
||||
|
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \ |
||||
|
COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \ |
||||
|
LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \ |
||||
|
KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS |
||||
|
from jinja2.environment import Environment |
||||
|
from jinja2.runtime import concat |
||||
|
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError |
||||
|
from jinja2.utils import contextfunction, import_string, Markup |
||||
|
from jinja2._compat import with_metaclass, string_types, iteritems |
||||
|
|
||||
|
|
||||
|
# the only real useful gettext functions for a Jinja template. Note |
||||
|
# that ugettext must be assigned to gettext as Jinja doesn't support |
||||
|
# non unicode strings. |
||||
|
GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext') |
||||
|
|
||||
|
|
||||
|
class ExtensionRegistry(type): |
||||
|
"""Gives the extension an unique identifier.""" |
||||
|
|
||||
|
def __new__(cls, name, bases, d): |
||||
|
rv = type.__new__(cls, name, bases, d) |
||||
|
rv.identifier = rv.__module__ + '.' + rv.__name__ |
||||
|
return rv |
||||
|
|
||||
|
|
||||
|
class Extension(with_metaclass(ExtensionRegistry, object)): |
||||
|
"""Extensions can be used to add extra functionality to the Jinja template |
||||
|
system at the parser level. Custom extensions are bound to an environment |
||||
|
but may not store environment specific data on `self`. The reason for |
||||
|
this is that an extension can be bound to another environment (for |
||||
|
overlays) by creating a copy and reassigning the `environment` attribute. |
||||
|
|
||||
|
As extensions are created by the environment they cannot accept any |
||||
|
arguments for configuration. One may want to work around that by using |
||||
|
a factory function, but that is not possible as extensions are identified |
||||
|
by their import name. The correct way to configure the extension is |
||||
|
storing the configuration values on the environment. Because this way the |
||||
|
environment ends up acting as central configuration storage the |
||||
|
attributes may clash which is why extensions have to ensure that the names |
||||
|
they choose for configuration are not too generic. ``prefix`` for example |
||||
|
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good |
||||
|
name as includes the name of the extension (fragment cache). |
||||
|
""" |
||||
|
|
||||
|
#: if this extension parses this is the list of tags it's listening to. |
||||
|
tags = set() |
||||
|
|
||||
|
#: the priority of that extension. This is especially useful for |
||||
|
#: extensions that preprocess values. A lower value means higher |
||||
|
#: priority. |
||||
|
#: |
||||
|
#: .. versionadded:: 2.4 |
||||
|
priority = 100 |
||||
|
|
||||
|
def __init__(self, environment): |
||||
|
self.environment = environment |
||||
|
|
||||
|
def bind(self, environment): |
||||
|
"""Create a copy of this extension bound to another environment.""" |
||||
|
rv = object.__new__(self.__class__) |
||||
|
rv.__dict__.update(self.__dict__) |
||||
|
rv.environment = environment |
||||
|
return rv |
||||
|
|
||||
|
def preprocess(self, source, name, filename=None): |
||||
|
"""This method is called before the actual lexing and can be used to |
||||
|
preprocess the source. The `filename` is optional. The return value |
||||
|
must be the preprocessed source. |
||||
|
""" |
||||
|
return source |
||||
|
|
||||
|
def filter_stream(self, stream): |
||||
|
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used |
||||
|
to filter tokens returned. This method has to return an iterable of |
||||
|
:class:`~jinja2.lexer.Token`\s, but it doesn't have to return a |
||||
|
:class:`~jinja2.lexer.TokenStream`. |
||||
|
|
||||
|
In the `ext` folder of the Jinja2 source distribution there is a file |
||||
|
called `inlinegettext.py` which implements a filter that utilizes this |
||||
|
method. |
||||
|
""" |
||||
|
return stream |
||||
|
|
||||
|
def parse(self, parser): |
||||
|
"""If any of the :attr:`tags` matched this method is called with the |
||||
|
parser as first argument. The token the parser stream is pointing at |
||||
|
is the name token that matched. This method has to return one or a |
||||
|
list of multiple nodes. |
||||
|
""" |
||||
|
raise NotImplementedError() |
||||
|
|
||||
|
def attr(self, name, lineno=None): |
||||
|
"""Return an attribute node for the current extension. This is useful |
||||
|
to pass constants on extensions to generated template code. |
||||
|
|
||||
|
:: |
||||
|
|
||||
|
self.attr('_my_attribute', lineno=lineno) |
||||
|
""" |
||||
|
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno) |
||||
|
|
||||
|
def call_method(self, name, args=None, kwargs=None, dyn_args=None, |
||||
|
dyn_kwargs=None, lineno=None): |
||||
|
"""Call a method of the extension. This is a shortcut for |
||||
|
:meth:`attr` + :class:`jinja2.nodes.Call`. |
||||
|
""" |
||||
|
if args is None: |
||||
|
args = [] |
||||
|
if kwargs is None: |
||||
|
kwargs = [] |
||||
|
return nodes.Call(self.attr(name, lineno=lineno), args, kwargs, |
||||
|
dyn_args, dyn_kwargs, lineno=lineno) |
||||
|
|
||||
|
|
||||
|
@contextfunction |
||||
|
def _gettext_alias(__context, *args, **kwargs): |
||||
|
return __context.call(__context.resolve('gettext'), *args, **kwargs) |
||||
|
|
||||
|
|
||||
|
def _make_new_gettext(func): |
||||
|
@contextfunction |
||||
|
def gettext(__context, __string, **variables): |
||||
|
rv = __context.call(func, __string) |
||||
|
if __context.eval_ctx.autoescape: |
||||
|
rv = Markup(rv) |
||||
|
return rv % variables |
||||
|
return gettext |
||||
|
|
||||
|
|
||||
|
def _make_new_ngettext(func): |
||||
|
@contextfunction |
||||
|
def ngettext(__context, __singular, __plural, __num, **variables): |
||||
|
variables.setdefault('num', __num) |
||||
|
rv = __context.call(func, __singular, __plural, __num) |
||||
|
if __context.eval_ctx.autoescape: |
||||
|
rv = Markup(rv) |
||||
|
return rv % variables |
||||
|
return ngettext |
||||
|
|
||||
|
|
||||
|
class InternationalizationExtension(Extension): |
||||
|
"""This extension adds gettext support to Jinja2.""" |
||||
|
tags = set(['trans']) |
||||
|
|
||||
|
# TODO: the i18n extension is currently reevaluating values in a few |
||||
|
# situations. Take this example: |
||||
|
# {% trans count=something() %}{{ count }} foo{% pluralize |
||||
|
# %}{{ count }} fooss{% endtrans %} |
||||
|
# something is called twice here. One time for the gettext value and |
||||
|
# the other time for the n-parameter of the ngettext function. |
||||
|
|
||||
|
def __init__(self, environment): |
||||
|
Extension.__init__(self, environment) |
||||
|
environment.globals['_'] = _gettext_alias |
||||
|
environment.extend( |
||||
|
install_gettext_translations=self._install, |
||||
|
install_null_translations=self._install_null, |
||||
|
install_gettext_callables=self._install_callables, |
||||
|
uninstall_gettext_translations=self._uninstall, |
||||
|
extract_translations=self._extract, |
||||
|
newstyle_gettext=False |
||||
|
) |
||||
|
|
||||
|
def _install(self, translations, newstyle=None): |
||||
|
gettext = getattr(translations, 'ugettext', None) |
||||
|
if gettext is None: |
||||
|
gettext = translations.gettext |
||||
|
ngettext = getattr(translations, 'ungettext', None) |
||||
|
if ngettext is None: |
||||
|
ngettext = translations.ngettext |
||||
|
self._install_callables(gettext, ngettext, newstyle) |
||||
|
|
||||
|
def _install_null(self, newstyle=None): |
||||
|
self._install_callables( |
||||
|
lambda x: x, |
||||
|
lambda s, p, n: (n != 1 and (p,) or (s,))[0], |
||||
|
newstyle |
||||
|
) |
||||
|
|
||||
|
def _install_callables(self, gettext, ngettext, newstyle=None): |
||||
|
if newstyle is not None: |
||||
|
self.environment.newstyle_gettext = newstyle |
||||
|
if self.environment.newstyle_gettext: |
||||
|
gettext = _make_new_gettext(gettext) |
||||
|
ngettext = _make_new_ngettext(ngettext) |
||||
|
self.environment.globals.update( |
||||
|
gettext=gettext, |
||||
|
ngettext=ngettext |
||||
|
) |
||||
|
|
||||
|
def _uninstall(self, translations): |
||||
|
for key in 'gettext', 'ngettext': |
||||
|
self.environment.globals.pop(key, None) |
||||
|
|
||||
|
def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS): |
||||
|
if isinstance(source, string_types): |
||||
|
source = self.environment.parse(source) |
||||
|
return extract_from_ast(source, gettext_functions) |
||||
|
|
||||
|
def parse(self, parser): |
||||
|
"""Parse a translatable tag.""" |
||||
|
lineno = next(parser.stream).lineno |
||||
|
num_called_num = False |
||||
|
|
||||
|
# find all the variables referenced. Additionally a variable can be |
||||
|
# defined in the body of the trans block too, but this is checked at |
||||
|
# a later state. |
||||
|
plural_expr = None |
||||
|
plural_expr_assignment = None |
||||
|
variables = {} |
||||
|
while parser.stream.current.type != 'block_end': |
||||
|
if variables: |
||||
|
parser.stream.expect('comma') |
||||
|
|
||||
|
# skip colon for python compatibility |
||||
|
if parser.stream.skip_if('colon'): |
||||
|
break |
||||
|
|
||||
|
name = parser.stream.expect('name') |
||||
|
if name.value in variables: |
||||
|
parser.fail('translatable variable %r defined twice.' % |
||||
|
name.value, name.lineno, |
||||
|
exc=TemplateAssertionError) |
||||
|
|
||||
|
# expressions |
||||
|
if parser.stream.current.type == 'assign': |
||||
|
next(parser.stream) |
||||
|
variables[name.value] = var = parser.parse_expression() |
||||
|
else: |
||||
|
variables[name.value] = var = nodes.Name(name.value, 'load') |
||||
|
|
||||
|
if plural_expr is None: |
||||
|
if isinstance(var, nodes.Call): |
||||
|
plural_expr = nodes.Name('_trans', 'load') |
||||
|
variables[name.value] = plural_expr |
||||
|
plural_expr_assignment = nodes.Assign( |
||||
|
nodes.Name('_trans', 'store'), var) |
||||
|
else: |
||||
|
plural_expr = var |
||||
|
num_called_num = name.value == 'num' |
||||
|
|
||||
|
parser.stream.expect('block_end') |
||||
|
|
||||
|
plural = plural_names = None |
||||
|
have_plural = False |
||||
|
referenced = set() |
||||
|
|
||||
|
# now parse until endtrans or pluralize |
||||
|
singular_names, singular = self._parse_block(parser, True) |
||||
|
if singular_names: |
||||
|
referenced.update(singular_names) |
||||
|
if plural_expr is None: |
||||
|
plural_expr = nodes.Name(singular_names[0], 'load') |
||||
|
num_called_num = singular_names[0] == 'num' |
||||
|
|
||||
|
# if we have a pluralize block, we parse that too |
||||
|
if parser.stream.current.test('name:pluralize'): |
||||
|
have_plural = True |
||||
|
next(parser.stream) |
||||
|
if parser.stream.current.type != 'block_end': |
||||
|
name = parser.stream.expect('name') |
||||
|
if name.value not in variables: |
||||
|
parser.fail('unknown variable %r for pluralization' % |
||||
|
name.value, name.lineno, |
||||
|
exc=TemplateAssertionError) |
||||
|
plural_expr = variables[name.value] |
||||
|
num_called_num = name.value == 'num' |
||||
|
parser.stream.expect('block_end') |
||||
|
plural_names, plural = self._parse_block(parser, False) |
||||
|
next(parser.stream) |
||||
|
referenced.update(plural_names) |
||||
|
else: |
||||
|
next(parser.stream) |
||||
|
|
||||
|
# register free names as simple name expressions |
||||
|
for var in referenced: |
||||
|
if var not in variables: |
||||
|
variables[var] = nodes.Name(var, 'load') |
||||
|
|
||||
|
if not have_plural: |
||||
|
plural_expr = None |
||||
|
elif plural_expr is None: |
||||
|
parser.fail('pluralize without variables', lineno) |
||||
|
|
||||
|
node = self._make_node(singular, plural, variables, plural_expr, |
||||
|
bool(referenced), |
||||
|
num_called_num and have_plural) |
||||
|
node.set_lineno(lineno) |
||||
|
if plural_expr_assignment is not None: |
||||
|
return [plural_expr_assignment, node] |
||||
|
else: |
||||
|
return node |
||||
|
|
||||
|
def _parse_block(self, parser, allow_pluralize): |
||||
|
"""Parse until the next block tag with a given name.""" |
||||
|
referenced = [] |
||||
|
buf = [] |
||||
|
while 1: |
||||
|
if parser.stream.current.type == 'data': |
||||
|
buf.append(parser.stream.current.value.replace('%', '%%')) |
||||
|
next(parser.stream) |
||||
|
elif parser.stream.current.type == 'variable_begin': |
||||
|
next(parser.stream) |
||||
|
name = parser.stream.expect('name').value |
||||
|
referenced.append(name) |
||||
|
buf.append('%%(%s)s' % name) |
||||
|
parser.stream.expect('variable_end') |
||||
|
elif parser.stream.current.type == 'block_begin': |
||||
|
next(parser.stream) |
||||
|
if parser.stream.current.test('name:endtrans'): |
||||
|
break |
||||
|
elif parser.stream.current.test('name:pluralize'): |
||||
|
if allow_pluralize: |
||||
|
break |
||||
|
parser.fail('a translatable section can have only one ' |
||||
|
'pluralize section') |
||||
|
parser.fail('control structures in translatable sections are ' |
||||
|
'not allowed') |
||||
|
elif parser.stream.eos: |
||||
|
parser.fail('unclosed translation block') |
||||
|
else: |
||||
|
assert False, 'internal parser error' |
||||
|
|
||||
|
return referenced, concat(buf) |
||||
|
|
||||
|
def _make_node(self, singular, plural, variables, plural_expr, |
||||
|
vars_referenced, num_called_num): |
||||
|
"""Generates a useful node from the data provided.""" |
||||
|
# no variables referenced? no need to escape for old style |
||||
|
# gettext invocations only if there are vars. |
||||
|
if not vars_referenced and not self.environment.newstyle_gettext: |
||||
|
singular = singular.replace('%%', '%') |
||||
|
if plural: |
||||
|
plural = plural.replace('%%', '%') |
||||
|
|
||||
|
# singular only: |
||||
|
if plural_expr is None: |
||||
|
gettext = nodes.Name('gettext', 'load') |
||||
|
node = nodes.Call(gettext, [nodes.Const(singular)], |
||||
|
[], None, None) |
||||
|
|
||||
|
# singular and plural |
||||
|
else: |
||||
|
ngettext = nodes.Name('ngettext', 'load') |
||||
|
node = nodes.Call(ngettext, [ |
||||
|
nodes.Const(singular), |
||||
|
nodes.Const(plural), |
||||
|
plural_expr |
||||
|
], [], None, None) |
||||
|
|
||||
|
# in case newstyle gettext is used, the method is powerful |
||||
|
# enough to handle the variable expansion and autoescape |
||||
|
# handling itself |
||||
|
if self.environment.newstyle_gettext: |
||||
|
for key, value in iteritems(variables): |
||||
|
# the function adds that later anyways in case num was |
||||
|
# called num, so just skip it. |
||||
|
if num_called_num and key == 'num': |
||||
|
continue |
||||
|
node.kwargs.append(nodes.Keyword(key, value)) |
||||
|
|
||||
|
# otherwise do that here |
||||
|
else: |
||||
|
# mark the return value as safe if we are in an |
||||
|
# environment with autoescaping turned on |
||||
|
node = nodes.MarkSafeIfAutoescape(node) |
||||
|
if variables: |
||||
|
node = nodes.Mod(node, nodes.Dict([ |
||||
|
nodes.Pair(nodes.Const(key), value) |
||||
|
for key, value in variables.items() |
||||
|
])) |
||||
|
return nodes.Output([node]) |
||||
|
|
||||
|
|
||||
|
class ExprStmtExtension(Extension): |
||||
|
"""Adds a `do` tag to Jinja2 that works like the print statement just |
||||
|
that it doesn't print the return value. |
||||
|
""" |
||||
|
tags = set(['do']) |
||||
|
|
||||
|
def parse(self, parser): |
||||
|
node = nodes.ExprStmt(lineno=next(parser.stream).lineno) |
||||
|
node.node = parser.parse_tuple() |
||||
|
return node |
||||
|
|
||||
|
|
||||
|
class LoopControlExtension(Extension): |
||||
|
"""Adds break and continue to the template engine.""" |
||||
|
tags = set(['break', 'continue']) |
||||
|
|
||||
|
def parse(self, parser): |
||||
|
token = next(parser.stream) |
||||
|
if token.value == 'break': |
||||
|
return nodes.Break(lineno=token.lineno) |
||||
|
return nodes.Continue(lineno=token.lineno) |
||||
|
|
||||
|
|
||||
|
class WithExtension(Extension): |
||||
|
"""Adds support for a django-like with block.""" |
||||
|
tags = set(['with']) |
||||
|
|
||||
|
def parse(self, parser): |
||||
|
node = nodes.Scope(lineno=next(parser.stream).lineno) |
||||
|
assignments = [] |
||||
|
while parser.stream.current.type != 'block_end': |
||||
|
lineno = parser.stream.current.lineno |
||||
|
if assignments: |
||||
|
parser.stream.expect('comma') |
||||
|
target = parser.parse_assign_target() |
||||
|
parser.stream.expect('assign') |
||||
|
expr = parser.parse_expression() |
||||
|
assignments.append(nodes.Assign(target, expr, lineno=lineno)) |
||||
|
node.body = assignments + \ |
||||
|
list(parser.parse_statements(('name:endwith',), |
||||
|
drop_needle=True)) |
||||
|
return node |
||||
|
|
||||
|
|
||||
|
class AutoEscapeExtension(Extension): |
||||
|
"""Changes auto escape rules for a scope.""" |
||||
|
tags = set(['autoescape']) |
||||
|
|
||||
|
def parse(self, parser): |
||||
|
node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno) |
||||
|
node.options = [ |
||||
|
nodes.Keyword('autoescape', parser.parse_expression()) |
||||
|
] |
||||
|
node.body = parser.parse_statements(('name:endautoescape',), |
||||
|
drop_needle=True) |
||||
|
return nodes.Scope([node]) |
||||
|
|
||||
|
|
||||
|
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, |
||||
|
babel_style=True): |
||||
|
"""Extract localizable strings from the given template node. Per |
||||
|
default this function returns matches in babel style that means non string |
||||
|
parameters as well as keyword arguments are returned as `None`. This |
||||
|
allows Babel to figure out what you really meant if you are using |
||||
|
gettext functions that allow keyword arguments for placeholder expansion. |
||||
|
If you don't want that behavior set the `babel_style` parameter to `False` |
||||
|
which causes only strings to be returned and parameters are always stored |
||||
|
in tuples. As a consequence invalid gettext calls (calls without a single |
||||
|
string parameter or string parameters after non-string parameters) are |
||||
|
skipped. |
||||
|
|
||||
|
This example explains the behavior: |
||||
|
|
||||
|
>>> from jinja2 import Environment |
||||
|
>>> env = Environment() |
||||
|
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}') |
||||
|
>>> list(extract_from_ast(node)) |
||||
|
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))] |
||||
|
>>> list(extract_from_ast(node, babel_style=False)) |
||||
|
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))] |
||||
|
|
||||
|
For every string found this function yields a ``(lineno, function, |
||||
|
message)`` tuple, where: |
||||
|
|
||||
|
* ``lineno`` is the number of the line on which the string was found, |
||||
|
* ``function`` is the name of the ``gettext`` function used (if the |
||||
|
string was extracted from embedded Python code), and |
||||
|
* ``message`` is the string itself (a ``unicode`` object, or a tuple |
||||
|
of ``unicode`` objects for functions with multiple string arguments). |
||||
|
|
||||
|
This extraction function operates on the AST and is because of that unable |
||||
|
to extract any comments. For comment support you have to use the babel |
||||
|
extraction interface or extract comments yourself. |
||||
|
""" |
||||
|
for node in node.find_all(nodes.Call): |
||||
|
if not isinstance(node.node, nodes.Name) or \ |
||||
|
node.node.name not in gettext_functions: |
||||
|
continue |
||||
|
|
||||
|
strings = [] |
||||
|
for arg in node.args: |
||||
|
if isinstance(arg, nodes.Const) and \ |
||||
|
isinstance(arg.value, string_types): |
||||
|
strings.append(arg.value) |
||||
|
else: |
||||
|
strings.append(None) |
||||
|
|
||||
|
for arg in node.kwargs: |
||||
|
strings.append(None) |
||||
|
if node.dyn_args is not None: |
||||
|
strings.append(None) |
||||
|
if node.dyn_kwargs is not None: |
||||
|
strings.append(None) |
||||
|
|
||||
|
if not babel_style: |
||||
|
strings = tuple(x for x in strings if x is not None) |
||||
|
if not strings: |
||||
|
continue |
||||
|
else: |
||||
|
if len(strings) == 1: |
||||
|
strings = strings[0] |
||||
|
else: |
||||
|
strings = tuple(strings) |
||||
|
yield node.lineno, node.node.name, strings |
||||
|
|
||||
|
|
||||
|
class _CommentFinder(object): |
||||
|
"""Helper class to find comments in a token stream. Can only |
||||
|
find comments for gettext calls forwards. Once the comment |
||||
|
from line 4 is found, a comment for line 1 will not return a |
||||
|
usable value. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, tokens, comment_tags): |
||||
|
self.tokens = tokens |
||||
|
self.comment_tags = comment_tags |
||||
|
self.offset = 0 |
||||
|
self.last_lineno = 0 |
||||
|
|
||||
|
def find_backwards(self, offset): |
||||
|
try: |
||||
|
for _, token_type, token_value in \ |
||||
|
reversed(self.tokens[self.offset:offset]): |
||||
|
if token_type in ('comment', 'linecomment'): |
||||
|
try: |
||||
|
prefix, comment = token_value.split(None, 1) |
||||
|
except ValueError: |
||||
|
continue |
||||
|
if prefix in self.comment_tags: |
||||
|
return [comment.rstrip()] |
||||
|
return [] |
||||
|
finally: |
||||
|
self.offset = offset |
||||
|
|
||||
|
def find_comments(self, lineno): |
||||
|
if not self.comment_tags or self.last_lineno > lineno: |
||||
|
return [] |
||||
|
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]): |
||||
|
if token_lineno > lineno: |
||||
|
return self.find_backwards(self.offset + idx) |
||||
|
return self.find_backwards(len(self.tokens)) |
||||
|
|
||||
|
|
||||
|
def babel_extract(fileobj, keywords, comment_tags, options): |
||||
|
"""Babel extraction method for Jinja templates. |
||||
|
|
||||
|
.. versionchanged:: 2.3 |
||||
|
Basic support for translation comments was added. If `comment_tags` |
||||
|
is now set to a list of keywords for extraction, the extractor will |
||||
|
try to find the best preceeding comment that begins with one of the |
||||
|
keywords. For best results, make sure to not have more than one |
||||
|
gettext call in one line of code and the matching comment in the |
||||
|
same line or the line before. |
||||
|
|
||||
|
.. versionchanged:: 2.5.1 |
||||
|
The `newstyle_gettext` flag can be set to `True` to enable newstyle |
||||
|
gettext calls. |
||||
|
|
||||
|
.. versionchanged:: 2.7 |
||||
|
A `silent` option can now be provided. If set to `False` template |
||||
|
syntax errors are propagated instead of being ignored. |
||||
|
|
||||
|
:param fileobj: the file-like object the messages should be extracted from |
||||
|
:param keywords: a list of keywords (i.e. function names) that should be |
||||
|
recognized as translation functions |
||||
|
:param comment_tags: a list of translator tags to search for and include |
||||
|
in the results. |
||||
|
:param options: a dictionary of additional options (optional) |
||||
|
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples. |
||||
|
(comments will be empty currently) |
||||
|
""" |
||||
|
extensions = set() |
||||
|
for extension in options.get('extensions', '').split(','): |
||||
|
extension = extension.strip() |
||||
|
if not extension: |
||||
|
continue |
||||
|
extensions.add(import_string(extension)) |
||||
|
if InternationalizationExtension not in extensions: |
||||
|
extensions.add(InternationalizationExtension) |
||||
|
|
||||
|
def getbool(options, key, default=False): |
||||
|
return options.get(key, str(default)).lower() in \ |
||||
|
('1', 'on', 'yes', 'true') |
||||
|
|
||||
|
silent = getbool(options, 'silent', True) |
||||
|
environment = Environment( |
||||
|
options.get('block_start_string', BLOCK_START_STRING), |
||||
|
options.get('block_end_string', BLOCK_END_STRING), |
||||
|
options.get('variable_start_string', VARIABLE_START_STRING), |
||||
|
options.get('variable_end_string', VARIABLE_END_STRING), |
||||
|
options.get('comment_start_string', COMMENT_START_STRING), |
||||
|
options.get('comment_end_string', COMMENT_END_STRING), |
||||
|
options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, |
||||
|
options.get('line_comment_prefix') or LINE_COMMENT_PREFIX, |
||||
|
getbool(options, 'trim_blocks', TRIM_BLOCKS), |
||||
|
getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS), |
||||
|
NEWLINE_SEQUENCE, |
||||
|
getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE), |
||||
|
frozenset(extensions), |
||||
|
cache_size=0, |
||||
|
auto_reload=False |
||||
|
) |
||||
|
|
||||
|
if getbool(options, 'newstyle_gettext'): |
||||
|
environment.newstyle_gettext = True |
||||
|
|
||||
|
source = fileobj.read().decode(options.get('encoding', 'utf-8')) |
||||
|
try: |
||||
|
node = environment.parse(source) |
||||
|
tokens = list(environment.lex(environment.preprocess(source))) |
||||
|
except TemplateSyntaxError as e: |
||||
|
if not silent: |
||||
|
raise |
||||
|
# skip templates with syntax errors |
||||
|
return |
||||
|
|
||||
|
finder = _CommentFinder(tokens, comment_tags) |
||||
|
for lineno, func, message in extract_from_ast(node, keywords): |
||||
|
yield lineno, func, message, finder.find_comments(lineno) |
||||
|
|
||||
|
|
||||
|
#: nicer import names |
||||
|
i18n = InternationalizationExtension |
||||
|
do = ExprStmtExtension |
||||
|
loopcontrols = LoopControlExtension |
||||
|
with_ = WithExtension |
||||
|
autoescape = AutoEscapeExtension |
@ -0,0 +1,996 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.filters |
||||
|
~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Bundled jinja filters. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
import re |
||||
|
import math |
||||
|
|
||||
|
from random import choice |
||||
|
from operator import itemgetter |
||||
|
from itertools import groupby |
||||
|
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \ |
||||
|
unicode_urlencode |
||||
|
from jinja2.runtime import Undefined |
||||
|
from jinja2.exceptions import FilterArgumentError |
||||
|
from jinja2._compat import imap, string_types, text_type, iteritems |
||||
|
|
||||
|
|
||||
|
_word_re = re.compile(r'\w+(?u)') |
||||
|
|
||||
|
|
||||
|
def contextfilter(f): |
||||
|
"""Decorator for marking context dependent filters. The current |
||||
|
:class:`Context` will be passed as first argument. |
||||
|
""" |
||||
|
f.contextfilter = True |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
def evalcontextfilter(f): |
||||
|
"""Decorator for marking eval-context dependent filters. An eval |
||||
|
context object is passed as first argument. For more information |
||||
|
about the eval context, see :ref:`eval-context`. |
||||
|
|
||||
|
.. versionadded:: 2.4 |
||||
|
""" |
||||
|
f.evalcontextfilter = True |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
def environmentfilter(f): |
||||
|
"""Decorator for marking environment dependent filters. The current |
||||
|
:class:`Environment` is passed to the filter as first argument. |
||||
|
""" |
||||
|
f.environmentfilter = True |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
def make_attrgetter(environment, attribute): |
||||
|
"""Returns a callable that looks up the given attribute from a |
||||
|
passed object with the rules of the environment. Dots are allowed |
||||
|
to access attributes of attributes. Integer parts in paths are |
||||
|
looked up as integers. |
||||
|
""" |
||||
|
if not isinstance(attribute, string_types) \ |
||||
|
or ('.' not in attribute and not attribute.isdigit()): |
||||
|
return lambda x: environment.getitem(x, attribute) |
||||
|
attribute = attribute.split('.') |
||||
|
def attrgetter(item): |
||||
|
for part in attribute: |
||||
|
if part.isdigit(): |
||||
|
part = int(part) |
||||
|
item = environment.getitem(item, part) |
||||
|
return item |
||||
|
return attrgetter |
||||
|
|
||||
|
|
||||
|
def do_forceescape(value): |
||||
|
"""Enforce HTML escaping. This will probably double escape variables.""" |
||||
|
if hasattr(value, '__html__'): |
||||
|
value = value.__html__() |
||||
|
return escape(text_type(value)) |
||||
|
|
||||
|
|
||||
|
def do_urlencode(value): |
||||
|
"""Escape strings for use in URLs (uses UTF-8 encoding). It accepts both |
||||
|
dictionaries and regular strings as well as pairwise iterables. |
||||
|
|
||||
|
.. versionadded:: 2.7 |
||||
|
""" |
||||
|
itemiter = None |
||||
|
if isinstance(value, dict): |
||||
|
itemiter = iteritems(value) |
||||
|
elif not isinstance(value, string_types): |
||||
|
try: |
||||
|
itemiter = iter(value) |
||||
|
except TypeError: |
||||
|
pass |
||||
|
if itemiter is None: |
||||
|
return unicode_urlencode(value) |
||||
|
return u'&'.join(unicode_urlencode(k) + '=' + |
||||
|
unicode_urlencode(v, for_qs=True) |
||||
|
for k, v in itemiter) |
||||
|
|
||||
|
|
||||
|
@evalcontextfilter |
||||
|
def do_replace(eval_ctx, s, old, new, count=None): |
||||
|
"""Return a copy of the value with all occurrences of a substring |
||||
|
replaced with a new one. The first argument is the substring |
||||
|
that should be replaced, the second is the replacement string. |
||||
|
If the optional third argument ``count`` is given, only the first |
||||
|
``count`` occurrences are replaced: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ "Hello World"|replace("Hello", "Goodbye") }} |
||||
|
-> Goodbye World |
||||
|
|
||||
|
{{ "aaaaargh"|replace("a", "d'oh, ", 2) }} |
||||
|
-> d'oh, d'oh, aaargh |
||||
|
""" |
||||
|
if count is None: |
||||
|
count = -1 |
||||
|
if not eval_ctx.autoescape: |
||||
|
return text_type(s).replace(text_type(old), text_type(new), count) |
||||
|
if hasattr(old, '__html__') or hasattr(new, '__html__') and \ |
||||
|
not hasattr(s, '__html__'): |
||||
|
s = escape(s) |
||||
|
else: |
||||
|
s = soft_unicode(s) |
||||
|
return s.replace(soft_unicode(old), soft_unicode(new), count) |
||||
|
|
||||
|
|
||||
|
def do_upper(s): |
||||
|
"""Convert a value to uppercase.""" |
||||
|
return soft_unicode(s).upper() |
||||
|
|
||||
|
|
||||
|
def do_lower(s): |
||||
|
"""Convert a value to lowercase.""" |
||||
|
return soft_unicode(s).lower() |
||||
|
|
||||
|
|
||||
|
@evalcontextfilter |
||||
|
def do_xmlattr(_eval_ctx, d, autospace=True): |
||||
|
"""Create an SGML/XML attribute string based on the items in a dict. |
||||
|
All values that are neither `none` nor `undefined` are automatically |
||||
|
escaped: |
||||
|
|
||||
|
.. sourcecode:: html+jinja |
||||
|
|
||||
|
<ul{{ {'class': 'my_list', 'missing': none, |
||||
|
'id': 'list-%d'|format(variable)}|xmlattr }}> |
||||
|
... |
||||
|
</ul> |
||||
|
|
||||
|
Results in something like this: |
||||
|
|
||||
|
.. sourcecode:: html |
||||
|
|
||||
|
<ul class="my_list" id="list-42"> |
||||
|
... |
||||
|
</ul> |
||||
|
|
||||
|
As you can see it automatically prepends a space in front of the item |
||||
|
if the filter returned something unless the second parameter is false. |
||||
|
""" |
||||
|
rv = u' '.join( |
||||
|
u'%s="%s"' % (escape(key), escape(value)) |
||||
|
for key, value in iteritems(d) |
||||
|
if value is not None and not isinstance(value, Undefined) |
||||
|
) |
||||
|
if autospace and rv: |
||||
|
rv = u' ' + rv |
||||
|
if _eval_ctx.autoescape: |
||||
|
rv = Markup(rv) |
||||
|
return rv |
||||
|
|
||||
|
|
||||
|
def do_capitalize(s): |
||||
|
"""Capitalize a value. The first character will be uppercase, all others |
||||
|
lowercase. |
||||
|
""" |
||||
|
return soft_unicode(s).capitalize() |
||||
|
|
||||
|
|
||||
|
def do_title(s): |
||||
|
"""Return a titlecased version of the value. I.e. words will start with |
||||
|
uppercase letters, all remaining characters are lowercase. |
||||
|
""" |
||||
|
rv = [] |
||||
|
for item in re.compile(r'([-\s]+)(?u)').split(soft_unicode(s)): |
||||
|
if not item: |
||||
|
continue |
||||
|
rv.append(item[0].upper() + item[1:].lower()) |
||||
|
return ''.join(rv) |
||||
|
|
||||
|
|
||||
|
def do_dictsort(value, case_sensitive=False, by='key'): |
||||
|
"""Sort a dict and yield (key, value) pairs. Because python dicts are |
||||
|
unsorted you may want to use this function to order them by either |
||||
|
key or value: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{% for item in mydict|dictsort %} |
||||
|
sort the dict by key, case insensitive |
||||
|
|
||||
|
{% for item in mydict|dictsort(true) %} |
||||
|
sort the dict by key, case sensitive |
||||
|
|
||||
|
{% for item in mydict|dictsort(false, 'value') %} |
||||
|
sort the dict by value, case insensitive |
||||
|
""" |
||||
|
if by == 'key': |
||||
|
pos = 0 |
||||
|
elif by == 'value': |
||||
|
pos = 1 |
||||
|
else: |
||||
|
raise FilterArgumentError('You can only sort by either ' |
||||
|
'"key" or "value"') |
||||
|
def sort_func(item): |
||||
|
value = item[pos] |
||||
|
if isinstance(value, string_types) and not case_sensitive: |
||||
|
value = value.lower() |
||||
|
return value |
||||
|
|
||||
|
return sorted(value.items(), key=sort_func) |
||||
|
|
||||
|
|
||||
|
@environmentfilter |
||||
|
def do_sort(environment, value, reverse=False, case_sensitive=False, |
||||
|
attribute=None): |
||||
|
"""Sort an iterable. Per default it sorts ascending, if you pass it |
||||
|
true as first argument it will reverse the sorting. |
||||
|
|
||||
|
If the iterable is made of strings the third parameter can be used to |
||||
|
control the case sensitiveness of the comparison which is disabled by |
||||
|
default. |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{% for item in iterable|sort %} |
||||
|
... |
||||
|
{% endfor %} |
||||
|
|
||||
|
It is also possible to sort by an attribute (for example to sort |
||||
|
by the date of an object) by specifying the `attribute` parameter: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{% for item in iterable|sort(attribute='date') %} |
||||
|
... |
||||
|
{% endfor %} |
||||
|
|
||||
|
.. versionchanged:: 2.6 |
||||
|
The `attribute` parameter was added. |
||||
|
""" |
||||
|
if not case_sensitive: |
||||
|
def sort_func(item): |
||||
|
if isinstance(item, string_types): |
||||
|
item = item.lower() |
||||
|
return item |
||||
|
else: |
||||
|
sort_func = None |
||||
|
if attribute is not None: |
||||
|
getter = make_attrgetter(environment, attribute) |
||||
|
def sort_func(item, processor=sort_func or (lambda x: x)): |
||||
|
return processor(getter(item)) |
||||
|
return sorted(value, key=sort_func, reverse=reverse) |
||||
|
|
||||
|
|
||||
|
def do_default(value, default_value=u'', boolean=False): |
||||
|
"""If the value is undefined it will return the passed default value, |
||||
|
otherwise the value of the variable: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ my_variable|default('my_variable is not defined') }} |
||||
|
|
||||
|
This will output the value of ``my_variable`` if the variable was |
||||
|
defined, otherwise ``'my_variable is not defined'``. If you want |
||||
|
to use default with variables that evaluate to false you have to |
||||
|
set the second parameter to `true`: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ ''|default('the string was empty', true) }} |
||||
|
""" |
||||
|
if isinstance(value, Undefined) or (boolean and not value): |
||||
|
return default_value |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@evalcontextfilter |
||||
|
def do_join(eval_ctx, value, d=u'', attribute=None): |
||||
|
"""Return a string which is the concatenation of the strings in the |
||||
|
sequence. The separator between elements is an empty string per |
||||
|
default, you can define it with the optional parameter: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ [1, 2, 3]|join('|') }} |
||||
|
-> 1|2|3 |
||||
|
|
||||
|
{{ [1, 2, 3]|join }} |
||||
|
-> 123 |
||||
|
|
||||
|
It is also possible to join certain attributes of an object: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ users|join(', ', attribute='username') }} |
||||
|
|
||||
|
.. versionadded:: 2.6 |
||||
|
The `attribute` parameter was added. |
||||
|
""" |
||||
|
if attribute is not None: |
||||
|
value = imap(make_attrgetter(eval_ctx.environment, attribute), value) |
||||
|
|
||||
|
# no automatic escaping? joining is a lot eaiser then |
||||
|
if not eval_ctx.autoescape: |
||||
|
return text_type(d).join(imap(text_type, value)) |
||||
|
|
||||
|
# if the delimiter doesn't have an html representation we check |
||||
|
# if any of the items has. If yes we do a coercion to Markup |
||||
|
if not hasattr(d, '__html__'): |
||||
|
value = list(value) |
||||
|
do_escape = False |
||||
|
for idx, item in enumerate(value): |
||||
|
if hasattr(item, '__html__'): |
||||
|
do_escape = True |
||||
|
else: |
||||
|
value[idx] = text_type(item) |
||||
|
if do_escape: |
||||
|
d = escape(d) |
||||
|
else: |
||||
|
d = text_type(d) |
||||
|
return d.join(value) |
||||
|
|
||||
|
# no html involved, to normal joining |
||||
|
return soft_unicode(d).join(imap(soft_unicode, value)) |
||||
|
|
||||
|
|
||||
|
def do_center(value, width=80): |
||||
|
"""Centers the value in a field of a given width.""" |
||||
|
return text_type(value).center(width) |
||||
|
|
||||
|
|
||||
|
@environmentfilter |
||||
|
def do_first(environment, seq): |
||||
|
"""Return the first item of a sequence.""" |
||||
|
try: |
||||
|
return next(iter(seq)) |
||||
|
except StopIteration: |
||||
|
return environment.undefined('No first item, sequence was empty.') |
||||
|
|
||||
|
|
||||
|
@environmentfilter |
||||
|
def do_last(environment, seq): |
||||
|
"""Return the last item of a sequence.""" |
||||
|
try: |
||||
|
return next(iter(reversed(seq))) |
||||
|
except StopIteration: |
||||
|
return environment.undefined('No last item, sequence was empty.') |
||||
|
|
||||
|
|
||||
|
@environmentfilter |
||||
|
def do_random(environment, seq): |
||||
|
"""Return a random item from the sequence.""" |
||||
|
try: |
||||
|
return choice(seq) |
||||
|
except IndexError: |
||||
|
return environment.undefined('No random item, sequence was empty.') |
||||
|
|
||||
|
|
||||
|
def do_filesizeformat(value, binary=False): |
||||
|
"""Format the value like a 'human-readable' file size (i.e. 13 kB, |
||||
|
4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega, |
||||
|
Giga, etc.), if the second parameter is set to `True` the binary |
||||
|
prefixes are used (Mebi, Gibi). |
||||
|
""" |
||||
|
bytes = float(value) |
||||
|
base = binary and 1024 or 1000 |
||||
|
prefixes = [ |
||||
|
(binary and 'KiB' or 'kB'), |
||||
|
(binary and 'MiB' or 'MB'), |
||||
|
(binary and 'GiB' or 'GB'), |
||||
|
(binary and 'TiB' or 'TB'), |
||||
|
(binary and 'PiB' or 'PB'), |
||||
|
(binary and 'EiB' or 'EB'), |
||||
|
(binary and 'ZiB' or 'ZB'), |
||||
|
(binary and 'YiB' or 'YB') |
||||
|
] |
||||
|
if bytes == 1: |
||||
|
return '1 Byte' |
||||
|
elif bytes < base: |
||||
|
return '%d Bytes' % bytes |
||||
|
else: |
||||
|
for i, prefix in enumerate(prefixes): |
||||
|
unit = base ** (i + 2) |
||||
|
if bytes < unit: |
||||
|
return '%.1f %s' % ((base * bytes / unit), prefix) |
||||
|
return '%.1f %s' % ((base * bytes / unit), prefix) |
||||
|
|
||||
|
|
||||
|
def do_pprint(value, verbose=False): |
||||
|
"""Pretty print a variable. Useful for debugging. |
||||
|
|
||||
|
With Jinja 1.2 onwards you can pass it a parameter. If this parameter |
||||
|
is truthy the output will be more verbose (this requires `pretty`) |
||||
|
""" |
||||
|
return pformat(value, verbose=verbose) |
||||
|
|
||||
|
|
||||
|
@evalcontextfilter |
||||
|
def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False, |
||||
|
target=None): |
||||
|
"""Converts URLs in plain text into clickable links. |
||||
|
|
||||
|
If you pass the filter an additional integer it will shorten the urls |
||||
|
to that number. Also a third argument exists that makes the urls |
||||
|
"nofollow": |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ mytext|urlize(40, true) }} |
||||
|
links are shortened to 40 chars and defined with rel="nofollow" |
||||
|
|
||||
|
If *target* is specified, the ``target`` attribute will be added to the |
||||
|
``<a>`` tag: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ mytext|urlize(40, target='_blank') }} |
||||
|
|
||||
|
.. versionchanged:: 2.8+ |
||||
|
The *target* parameter was added. |
||||
|
""" |
||||
|
rv = urlize(value, trim_url_limit, nofollow, target) |
||||
|
if eval_ctx.autoescape: |
||||
|
rv = Markup(rv) |
||||
|
return rv |
||||
|
|
||||
|
|
||||
|
def do_indent(s, width=4, indentfirst=False): |
||||
|
"""Return a copy of the passed string, each line indented by |
||||
|
4 spaces. The first line is not indented. If you want to |
||||
|
change the number of spaces or indent the first line too |
||||
|
you can pass additional parameters to the filter: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ mytext|indent(2, true) }} |
||||
|
indent by two spaces and indent the first line too. |
||||
|
""" |
||||
|
indention = u' ' * width |
||||
|
rv = (u'\n' + indention).join(s.splitlines()) |
||||
|
if indentfirst: |
||||
|
rv = indention + rv |
||||
|
return rv |
||||
|
|
||||
|
|
||||
|
def do_truncate(s, length=255, killwords=False, end='...'): |
||||
|
"""Return a truncated copy of the string. The length is specified |
||||
|
with the first parameter which defaults to ``255``. If the second |
||||
|
parameter is ``true`` the filter will cut the text at length. Otherwise |
||||
|
it will discard the last word. If the text was in fact |
||||
|
truncated it will append an ellipsis sign (``"..."``). If you want a |
||||
|
different ellipsis sign than ``"..."`` you can specify it using the |
||||
|
third parameter. |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ "foo bar baz"|truncate(9) }} |
||||
|
-> "foo ..." |
||||
|
{{ "foo bar baz"|truncate(9, True) }} |
||||
|
-> "foo ba..." |
||||
|
|
||||
|
""" |
||||
|
if len(s) <= length: |
||||
|
return s |
||||
|
elif killwords: |
||||
|
return s[:length - len(end)] + end |
||||
|
|
||||
|
result = s[:length - len(end)].rsplit(' ', 1)[0] |
||||
|
if len(result) < length: |
||||
|
result += ' ' |
||||
|
return result + end |
||||
|
|
||||
|
|
||||
|
@environmentfilter |
||||
|
def do_wordwrap(environment, s, width=79, break_long_words=True, |
||||
|
wrapstring=None): |
||||
|
""" |
||||
|
Return a copy of the string passed to the filter wrapped after |
||||
|
``79`` characters. You can override this default using the first |
||||
|
parameter. If you set the second parameter to `false` Jinja will not |
||||
|
split words apart if they are longer than `width`. By default, the newlines |
||||
|
will be the default newlines for the environment, but this can be changed |
||||
|
using the wrapstring keyword argument. |
||||
|
|
||||
|
.. versionadded:: 2.7 |
||||
|
Added support for the `wrapstring` parameter. |
||||
|
""" |
||||
|
if not wrapstring: |
||||
|
wrapstring = environment.newline_sequence |
||||
|
import textwrap |
||||
|
return wrapstring.join(textwrap.wrap(s, width=width, expand_tabs=False, |
||||
|
replace_whitespace=False, |
||||
|
break_long_words=break_long_words)) |
||||
|
|
||||
|
|
||||
|
def do_wordcount(s): |
||||
|
"""Count the words in that string.""" |
||||
|
return len(_word_re.findall(s)) |
||||
|
|
||||
|
|
||||
|
def do_int(value, default=0, base=10): |
||||
|
"""Convert the value into an integer. If the |
||||
|
conversion doesn't work it will return ``0``. You can |
||||
|
override this default using the first parameter. You |
||||
|
can also override the default base (10) in the second |
||||
|
parameter, which handles input with prefixes such as |
||||
|
0b, 0o and 0x for bases 2, 8 and 16 respectively. |
||||
|
""" |
||||
|
try: |
||||
|
return int(value, base) |
||||
|
except (TypeError, ValueError): |
||||
|
# this quirk is necessary so that "42.23"|int gives 42. |
||||
|
try: |
||||
|
return int(float(value)) |
||||
|
except (TypeError, ValueError): |
||||
|
return default |
||||
|
|
||||
|
|
||||
|
def do_float(value, default=0.0): |
||||
|
"""Convert the value into a floating point number. If the |
||||
|
conversion doesn't work it will return ``0.0``. You can |
||||
|
override this default using the first parameter. |
||||
|
""" |
||||
|
try: |
||||
|
return float(value) |
||||
|
except (TypeError, ValueError): |
||||
|
return default |
||||
|
|
||||
|
|
||||
|
def do_format(value, *args, **kwargs): |
||||
|
""" |
||||
|
Apply python string formatting on an object: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ "%s - %s"|format("Hello?", "Foo!") }} |
||||
|
-> Hello? - Foo! |
||||
|
""" |
||||
|
if args and kwargs: |
||||
|
raise FilterArgumentError('can\'t handle positional and keyword ' |
||||
|
'arguments at the same time') |
||||
|
return soft_unicode(value) % (kwargs or args) |
||||
|
|
||||
|
|
||||
|
def do_trim(value): |
||||
|
"""Strip leading and trailing whitespace.""" |
||||
|
return soft_unicode(value).strip() |
||||
|
|
||||
|
|
||||
|
def do_striptags(value): |
||||
|
"""Strip SGML/XML tags and replace adjacent whitespace by one space. |
||||
|
""" |
||||
|
if hasattr(value, '__html__'): |
||||
|
value = value.__html__() |
||||
|
return Markup(text_type(value)).striptags() |
||||
|
|
||||
|
|
||||
|
def do_slice(value, slices, fill_with=None): |
||||
|
"""Slice an iterator and return a list of lists containing |
||||
|
those items. Useful if you want to create a div containing |
||||
|
three ul tags that represent columns: |
||||
|
|
||||
|
.. sourcecode:: html+jinja |
||||
|
|
||||
|
<div class="columwrapper"> |
||||
|
{%- for column in items|slice(3) %} |
||||
|
<ul class="column-{{ loop.index }}"> |
||||
|
{%- for item in column %} |
||||
|
<li>{{ item }}</li> |
||||
|
{%- endfor %} |
||||
|
</ul> |
||||
|
{%- endfor %} |
||||
|
</div> |
||||
|
|
||||
|
If you pass it a second argument it's used to fill missing |
||||
|
values on the last iteration. |
||||
|
""" |
||||
|
seq = list(value) |
||||
|
length = len(seq) |
||||
|
items_per_slice = length // slices |
||||
|
slices_with_extra = length % slices |
||||
|
offset = 0 |
||||
|
for slice_number in range(slices): |
||||
|
start = offset + slice_number * items_per_slice |
||||
|
if slice_number < slices_with_extra: |
||||
|
offset += 1 |
||||
|
end = offset + (slice_number + 1) * items_per_slice |
||||
|
tmp = seq[start:end] |
||||
|
if fill_with is not None and slice_number >= slices_with_extra: |
||||
|
tmp.append(fill_with) |
||||
|
yield tmp |
||||
|
|
||||
|
|
||||
|
def do_batch(value, linecount, fill_with=None): |
||||
|
""" |
||||
|
A filter that batches items. It works pretty much like `slice` |
||||
|
just the other way round. It returns a list of lists with the |
||||
|
given number of items. If you provide a second parameter this |
||||
|
is used to fill up missing items. See this example: |
||||
|
|
||||
|
.. sourcecode:: html+jinja |
||||
|
|
||||
|
<table> |
||||
|
{%- for row in items|batch(3, ' ') %} |
||||
|
<tr> |
||||
|
{%- for column in row %} |
||||
|
<td>{{ column }}</td> |
||||
|
{%- endfor %} |
||||
|
</tr> |
||||
|
{%- endfor %} |
||||
|
</table> |
||||
|
""" |
||||
|
tmp = [] |
||||
|
for item in value: |
||||
|
if len(tmp) == linecount: |
||||
|
yield tmp |
||||
|
tmp = [] |
||||
|
tmp.append(item) |
||||
|
if tmp: |
||||
|
if fill_with is not None and len(tmp) < linecount: |
||||
|
tmp += [fill_with] * (linecount - len(tmp)) |
||||
|
yield tmp |
||||
|
|
||||
|
|
||||
|
def do_round(value, precision=0, method='common'): |
||||
|
"""Round the number to a given precision. The first |
||||
|
parameter specifies the precision (default is ``0``), the |
||||
|
second the rounding method: |
||||
|
|
||||
|
- ``'common'`` rounds either up or down |
||||
|
- ``'ceil'`` always rounds up |
||||
|
- ``'floor'`` always rounds down |
||||
|
|
||||
|
If you don't specify a method ``'common'`` is used. |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ 42.55|round }} |
||||
|
-> 43.0 |
||||
|
{{ 42.55|round(1, 'floor') }} |
||||
|
-> 42.5 |
||||
|
|
||||
|
Note that even if rounded to 0 precision, a float is returned. If |
||||
|
you need a real integer, pipe it through `int`: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ 42.55|round|int }} |
||||
|
-> 43 |
||||
|
""" |
||||
|
if not method in ('common', 'ceil', 'floor'): |
||||
|
raise FilterArgumentError('method must be common, ceil or floor') |
||||
|
if method == 'common': |
||||
|
return round(value, precision) |
||||
|
func = getattr(math, method) |
||||
|
return func(value * (10 ** precision)) / (10 ** precision) |
||||
|
|
||||
|
|
||||
|
@environmentfilter |
||||
|
def do_groupby(environment, value, attribute): |
||||
|
"""Group a sequence of objects by a common attribute. |
||||
|
|
||||
|
If you for example have a list of dicts or objects that represent persons |
||||
|
with `gender`, `first_name` and `last_name` attributes and you want to |
||||
|
group all users by genders you can do something like the following |
||||
|
snippet: |
||||
|
|
||||
|
.. sourcecode:: html+jinja |
||||
|
|
||||
|
<ul> |
||||
|
{% for group in persons|groupby('gender') %} |
||||
|
<li>{{ group.grouper }}<ul> |
||||
|
{% for person in group.list %} |
||||
|
<li>{{ person.first_name }} {{ person.last_name }}</li> |
||||
|
{% endfor %}</ul></li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
|
||||
|
Additionally it's possible to use tuple unpacking for the grouper and |
||||
|
list: |
||||
|
|
||||
|
.. sourcecode:: html+jinja |
||||
|
|
||||
|
<ul> |
||||
|
{% for grouper, list in persons|groupby('gender') %} |
||||
|
... |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
|
||||
|
As you can see the item we're grouping by is stored in the `grouper` |
||||
|
attribute and the `list` contains all the objects that have this grouper |
||||
|
in common. |
||||
|
|
||||
|
.. versionchanged:: 2.6 |
||||
|
It's now possible to use dotted notation to group by the child |
||||
|
attribute of another attribute. |
||||
|
""" |
||||
|
expr = make_attrgetter(environment, attribute) |
||||
|
return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr))) |
||||
|
|
||||
|
|
||||
|
class _GroupTuple(tuple): |
||||
|
__slots__ = () |
||||
|
grouper = property(itemgetter(0)) |
||||
|
list = property(itemgetter(1)) |
||||
|
|
||||
|
def __new__(cls, xxx_todo_changeme): |
||||
|
(key, value) = xxx_todo_changeme |
||||
|
return tuple.__new__(cls, (key, list(value))) |
||||
|
|
||||
|
|
||||
|
@environmentfilter |
||||
|
def do_sum(environment, iterable, attribute=None, start=0): |
||||
|
"""Returns the sum of a sequence of numbers plus the value of parameter |
||||
|
'start' (which defaults to 0). When the sequence is empty it returns |
||||
|
start. |
||||
|
|
||||
|
It is also possible to sum up only certain attributes: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
Total: {{ items|sum(attribute='price') }} |
||||
|
|
||||
|
.. versionchanged:: 2.6 |
||||
|
The `attribute` parameter was added to allow suming up over |
||||
|
attributes. Also the `start` parameter was moved on to the right. |
||||
|
""" |
||||
|
if attribute is not None: |
||||
|
iterable = imap(make_attrgetter(environment, attribute), iterable) |
||||
|
return sum(iterable, start) |
||||
|
|
||||
|
|
||||
|
def do_list(value): |
||||
|
"""Convert the value into a list. If it was a string the returned list |
||||
|
will be a list of characters. |
||||
|
""" |
||||
|
return list(value) |
||||
|
|
||||
|
|
||||
|
def do_mark_safe(value): |
||||
|
"""Mark the value as safe which means that in an environment with automatic |
||||
|
escaping enabled this variable will not be escaped. |
||||
|
""" |
||||
|
return Markup(value) |
||||
|
|
||||
|
|
||||
|
def do_mark_unsafe(value): |
||||
|
"""Mark a value as unsafe. This is the reverse operation for :func:`safe`.""" |
||||
|
return text_type(value) |
||||
|
|
||||
|
|
||||
|
def do_reverse(value): |
||||
|
"""Reverse the object or return an iterator that iterates over it the other |
||||
|
way round. |
||||
|
""" |
||||
|
if isinstance(value, string_types): |
||||
|
return value[::-1] |
||||
|
try: |
||||
|
return reversed(value) |
||||
|
except TypeError: |
||||
|
try: |
||||
|
rv = list(value) |
||||
|
rv.reverse() |
||||
|
return rv |
||||
|
except TypeError: |
||||
|
raise FilterArgumentError('argument must be iterable') |
||||
|
|
||||
|
|
||||
|
@environmentfilter |
||||
|
def do_attr(environment, obj, name): |
||||
|
"""Get an attribute of an object. ``foo|attr("bar")`` works like |
||||
|
``foo.bar`` just that always an attribute is returned and items are not |
||||
|
looked up. |
||||
|
|
||||
|
See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details. |
||||
|
""" |
||||
|
try: |
||||
|
name = str(name) |
||||
|
except UnicodeError: |
||||
|
pass |
||||
|
else: |
||||
|
try: |
||||
|
value = getattr(obj, name) |
||||
|
except AttributeError: |
||||
|
pass |
||||
|
else: |
||||
|
if environment.sandboxed and not \ |
||||
|
environment.is_safe_attribute(obj, name, value): |
||||
|
return environment.unsafe_undefined(obj, name) |
||||
|
return value |
||||
|
return environment.undefined(obj=obj, name=name) |
||||
|
|
||||
|
|
||||
|
@contextfilter |
||||
|
def do_map(*args, **kwargs): |
||||
|
"""Applies a filter on a sequence of objects or looks up an attribute. |
||||
|
This is useful when dealing with lists of objects but you are really |
||||
|
only interested in a certain value of it. |
||||
|
|
||||
|
The basic usage is mapping on an attribute. Imagine you have a list |
||||
|
of users but you are only interested in a list of usernames: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
Users on this page: {{ users|map(attribute='username')|join(', ') }} |
||||
|
|
||||
|
Alternatively you can let it invoke a filter by passing the name of the |
||||
|
filter and the arguments afterwards. A good example would be applying a |
||||
|
text conversion filter on a sequence: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
Users on this page: {{ titles|map('lower')|join(', ') }} |
||||
|
|
||||
|
.. versionadded:: 2.7 |
||||
|
""" |
||||
|
context = args[0] |
||||
|
seq = args[1] |
||||
|
|
||||
|
if len(args) == 2 and 'attribute' in kwargs: |
||||
|
attribute = kwargs.pop('attribute') |
||||
|
if kwargs: |
||||
|
raise FilterArgumentError('Unexpected keyword argument %r' % |
||||
|
next(iter(kwargs))) |
||||
|
func = make_attrgetter(context.environment, attribute) |
||||
|
else: |
||||
|
try: |
||||
|
name = args[2] |
||||
|
args = args[3:] |
||||
|
except LookupError: |
||||
|
raise FilterArgumentError('map requires a filter argument') |
||||
|
func = lambda item: context.environment.call_filter( |
||||
|
name, item, args, kwargs, context=context) |
||||
|
|
||||
|
if seq: |
||||
|
for item in seq: |
||||
|
yield func(item) |
||||
|
|
||||
|
|
||||
|
@contextfilter |
||||
|
def do_select(*args, **kwargs): |
||||
|
"""Filters a sequence of objects by applying a test to the object and only |
||||
|
selecting the ones with the test succeeding. |
||||
|
|
||||
|
Example usage: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ numbers|select("odd") }} |
||||
|
{{ numbers|select("odd") }} |
||||
|
|
||||
|
.. versionadded:: 2.7 |
||||
|
""" |
||||
|
return _select_or_reject(args, kwargs, lambda x: x, False) |
||||
|
|
||||
|
|
||||
|
@contextfilter |
||||
|
def do_reject(*args, **kwargs): |
||||
|
"""Filters a sequence of objects by applying a test to the object and |
||||
|
rejecting the ones with the test succeeding. |
||||
|
|
||||
|
Example usage: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ numbers|reject("odd") }} |
||||
|
|
||||
|
.. versionadded:: 2.7 |
||||
|
""" |
||||
|
return _select_or_reject(args, kwargs, lambda x: not x, False) |
||||
|
|
||||
|
|
||||
|
@contextfilter |
||||
|
def do_selectattr(*args, **kwargs): |
||||
|
"""Filters a sequence of objects by applying a test to an attribute of an |
||||
|
object and only selecting the ones with the test succeeding. |
||||
|
|
||||
|
Example usage: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ users|selectattr("is_active") }} |
||||
|
{{ users|selectattr("email", "none") }} |
||||
|
|
||||
|
.. versionadded:: 2.7 |
||||
|
""" |
||||
|
return _select_or_reject(args, kwargs, lambda x: x, True) |
||||
|
|
||||
|
|
||||
|
@contextfilter |
||||
|
def do_rejectattr(*args, **kwargs): |
||||
|
"""Filters a sequence of objects by applying a test to an attribute of an |
||||
|
object or the attribute and rejecting the ones with the test succeeding. |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ users|rejectattr("is_active") }} |
||||
|
{{ users|rejectattr("email", "none") }} |
||||
|
|
||||
|
.. versionadded:: 2.7 |
||||
|
""" |
||||
|
return _select_or_reject(args, kwargs, lambda x: not x, True) |
||||
|
|
||||
|
|
||||
|
def _select_or_reject(args, kwargs, modfunc, lookup_attr): |
||||
|
context = args[0] |
||||
|
seq = args[1] |
||||
|
if lookup_attr: |
||||
|
try: |
||||
|
attr = args[2] |
||||
|
except LookupError: |
||||
|
raise FilterArgumentError('Missing parameter for attribute name') |
||||
|
transfunc = make_attrgetter(context.environment, attr) |
||||
|
off = 1 |
||||
|
else: |
||||
|
off = 0 |
||||
|
transfunc = lambda x: x |
||||
|
|
||||
|
try: |
||||
|
name = args[2 + off] |
||||
|
args = args[3 + off:] |
||||
|
func = lambda item: context.environment.call_test( |
||||
|
name, item, args, kwargs) |
||||
|
except LookupError: |
||||
|
func = bool |
||||
|
|
||||
|
if seq: |
||||
|
for item in seq: |
||||
|
if modfunc(func(transfunc(item))): |
||||
|
yield item |
||||
|
|
||||
|
|
||||
|
FILTERS = { |
||||
|
'abs': abs, |
||||
|
'attr': do_attr, |
||||
|
'batch': do_batch, |
||||
|
'capitalize': do_capitalize, |
||||
|
'center': do_center, |
||||
|
'count': len, |
||||
|
'd': do_default, |
||||
|
'default': do_default, |
||||
|
'dictsort': do_dictsort, |
||||
|
'e': escape, |
||||
|
'escape': escape, |
||||
|
'filesizeformat': do_filesizeformat, |
||||
|
'first': do_first, |
||||
|
'float': do_float, |
||||
|
'forceescape': do_forceescape, |
||||
|
'format': do_format, |
||||
|
'groupby': do_groupby, |
||||
|
'indent': do_indent, |
||||
|
'int': do_int, |
||||
|
'join': do_join, |
||||
|
'last': do_last, |
||||
|
'length': len, |
||||
|
'list': do_list, |
||||
|
'lower': do_lower, |
||||
|
'map': do_map, |
||||
|
'pprint': do_pprint, |
||||
|
'random': do_random, |
||||
|
'reject': do_reject, |
||||
|
'rejectattr': do_rejectattr, |
||||
|
'replace': do_replace, |
||||
|
'reverse': do_reverse, |
||||
|
'round': do_round, |
||||
|
'safe': do_mark_safe, |
||||
|
'select': do_select, |
||||
|
'selectattr': do_selectattr, |
||||
|
'slice': do_slice, |
||||
|
'sort': do_sort, |
||||
|
'string': soft_unicode, |
||||
|
'striptags': do_striptags, |
||||
|
'sum': do_sum, |
||||
|
'title': do_title, |
||||
|
'trim': do_trim, |
||||
|
'truncate': do_truncate, |
||||
|
'upper': do_upper, |
||||
|
'urlencode': do_urlencode, |
||||
|
'urlize': do_urlize, |
||||
|
'wordcount': do_wordcount, |
||||
|
'wordwrap': do_wordwrap, |
||||
|
'xmlattr': do_xmlattr, |
||||
|
} |
@ -0,0 +1,734 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.lexer |
||||
|
~~~~~~~~~~~~ |
||||
|
|
||||
|
This module implements a Jinja / Python combination lexer. The |
||||
|
`Lexer` class provided by this module is used to do some preprocessing |
||||
|
for Jinja. |
||||
|
|
||||
|
On the one hand it filters out invalid operators like the bitshift |
||||
|
operators we don't allow in templates. On the other hand it separates |
||||
|
template code and python code in expressions. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
import re |
||||
|
|
||||
|
from operator import itemgetter |
||||
|
from collections import deque |
||||
|
from jinja2.exceptions import TemplateSyntaxError |
||||
|
from jinja2.utils import LRUCache |
||||
|
from jinja2._compat import iteritems, implements_iterator, text_type, \ |
||||
|
intern, PY2 |
||||
|
|
||||
|
|
||||
|
# cache for the lexers. Exists in order to be able to have multiple |
||||
|
# environments with the same lexer |
||||
|
_lexer_cache = LRUCache(50) |
||||
|
|
||||
|
# static regular expressions |
||||
|
whitespace_re = re.compile(r'\s+', re.U) |
||||
|
string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'" |
||||
|
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) |
||||
|
integer_re = re.compile(r'\d+') |
||||
|
|
||||
|
# we use the unicode identifier rule if this python version is able |
||||
|
# to handle unicode identifiers, otherwise the standard ASCII one. |
||||
|
try: |
||||
|
compile('föö', '<unknown>', 'eval') |
||||
|
except SyntaxError: |
||||
|
name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b') |
||||
|
else: |
||||
|
from jinja2 import _stringdefs |
||||
|
name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start, |
||||
|
_stringdefs.xid_continue)) |
||||
|
|
||||
|
float_re = re.compile(r'(?<!\.)\d+\.\d+') |
||||
|
newline_re = re.compile(r'(\r\n|\r|\n)') |
||||
|
|
||||
|
# internal the tokens and keep references to them |
||||
|
TOKEN_ADD = intern('add') |
||||
|
TOKEN_ASSIGN = intern('assign') |
||||
|
TOKEN_COLON = intern('colon') |
||||
|
TOKEN_COMMA = intern('comma') |
||||
|
TOKEN_DIV = intern('div') |
||||
|
TOKEN_DOT = intern('dot') |
||||
|
TOKEN_EQ = intern('eq') |
||||
|
TOKEN_FLOORDIV = intern('floordiv') |
||||
|
TOKEN_GT = intern('gt') |
||||
|
TOKEN_GTEQ = intern('gteq') |
||||
|
TOKEN_LBRACE = intern('lbrace') |
||||
|
TOKEN_LBRACKET = intern('lbracket') |
||||
|
TOKEN_LPAREN = intern('lparen') |
||||
|
TOKEN_LT = intern('lt') |
||||
|
TOKEN_LTEQ = intern('lteq') |
||||
|
TOKEN_MOD = intern('mod') |
||||
|
TOKEN_MUL = intern('mul') |
||||
|
TOKEN_NE = intern('ne') |
||||
|
TOKEN_PIPE = intern('pipe') |
||||
|
TOKEN_POW = intern('pow') |
||||
|
TOKEN_RBRACE = intern('rbrace') |
||||
|
TOKEN_RBRACKET = intern('rbracket') |
||||
|
TOKEN_RPAREN = intern('rparen') |
||||
|
TOKEN_SEMICOLON = intern('semicolon') |
||||
|
TOKEN_SUB = intern('sub') |
||||
|
TOKEN_TILDE = intern('tilde') |
||||
|
TOKEN_WHITESPACE = intern('whitespace') |
||||
|
TOKEN_FLOAT = intern('float') |
||||
|
TOKEN_INTEGER = intern('integer') |
||||
|
TOKEN_NAME = intern('name') |
||||
|
TOKEN_STRING = intern('string') |
||||
|
TOKEN_OPERATOR = intern('operator') |
||||
|
TOKEN_BLOCK_BEGIN = intern('block_begin') |
||||
|
TOKEN_BLOCK_END = intern('block_end') |
||||
|
TOKEN_VARIABLE_BEGIN = intern('variable_begin') |
||||
|
TOKEN_VARIABLE_END = intern('variable_end') |
||||
|
TOKEN_RAW_BEGIN = intern('raw_begin') |
||||
|
TOKEN_RAW_END = intern('raw_end') |
||||
|
TOKEN_COMMENT_BEGIN = intern('comment_begin') |
||||
|
TOKEN_COMMENT_END = intern('comment_end') |
||||
|
TOKEN_COMMENT = intern('comment') |
||||
|
TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin') |
||||
|
TOKEN_LINESTATEMENT_END = intern('linestatement_end') |
||||
|
TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin') |
||||
|
TOKEN_LINECOMMENT_END = intern('linecomment_end') |
||||
|
TOKEN_LINECOMMENT = intern('linecomment') |
||||
|
TOKEN_DATA = intern('data') |
||||
|
TOKEN_INITIAL = intern('initial') |
||||
|
TOKEN_EOF = intern('eof') |
||||
|
|
||||
|
# bind operators to token types |
||||
|
operators = { |
||||
|
'+': TOKEN_ADD, |
||||
|
'-': TOKEN_SUB, |
||||
|
'/': TOKEN_DIV, |
||||
|
'//': TOKEN_FLOORDIV, |
||||
|
'*': TOKEN_MUL, |
||||
|
'%': TOKEN_MOD, |
||||
|
'**': TOKEN_POW, |
||||
|
'~': TOKEN_TILDE, |
||||
|
'[': TOKEN_LBRACKET, |
||||
|
']': TOKEN_RBRACKET, |
||||
|
'(': TOKEN_LPAREN, |
||||
|
')': TOKEN_RPAREN, |
||||
|
'{': TOKEN_LBRACE, |
||||
|
'}': TOKEN_RBRACE, |
||||
|
'==': TOKEN_EQ, |
||||
|
'!=': TOKEN_NE, |
||||
|
'>': TOKEN_GT, |
||||
|
'>=': TOKEN_GTEQ, |
||||
|
'<': TOKEN_LT, |
||||
|
'<=': TOKEN_LTEQ, |
||||
|
'=': TOKEN_ASSIGN, |
||||
|
'.': TOKEN_DOT, |
||||
|
':': TOKEN_COLON, |
||||
|
'|': TOKEN_PIPE, |
||||
|
',': TOKEN_COMMA, |
||||
|
';': TOKEN_SEMICOLON |
||||
|
} |
||||
|
|
||||
|
reverse_operators = dict([(v, k) for k, v in iteritems(operators)]) |
||||
|
assert len(operators) == len(reverse_operators), 'operators dropped' |
||||
|
operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in |
||||
|
sorted(operators, key=lambda x: -len(x)))) |
||||
|
|
||||
|
ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT, |
||||
|
TOKEN_COMMENT_END, TOKEN_WHITESPACE, |
||||
|
TOKEN_LINECOMMENT_BEGIN, TOKEN_LINECOMMENT_END, |
||||
|
TOKEN_LINECOMMENT]) |
||||
|
ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA, |
||||
|
TOKEN_COMMENT, TOKEN_LINECOMMENT]) |
||||
|
|
||||
|
|
||||
|
def _describe_token_type(token_type): |
||||
|
if token_type in reverse_operators: |
||||
|
return reverse_operators[token_type] |
||||
|
return { |
||||
|
TOKEN_COMMENT_BEGIN: 'begin of comment', |
||||
|
TOKEN_COMMENT_END: 'end of comment', |
||||
|
TOKEN_COMMENT: 'comment', |
||||
|
TOKEN_LINECOMMENT: 'comment', |
||||
|
TOKEN_BLOCK_BEGIN: 'begin of statement block', |
||||
|
TOKEN_BLOCK_END: 'end of statement block', |
||||
|
TOKEN_VARIABLE_BEGIN: 'begin of print statement', |
||||
|
TOKEN_VARIABLE_END: 'end of print statement', |
||||
|
TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement', |
||||
|
TOKEN_LINESTATEMENT_END: 'end of line statement', |
||||
|
TOKEN_DATA: 'template data / text', |
||||
|
TOKEN_EOF: 'end of template' |
||||
|
}.get(token_type, token_type) |
||||
|
|
||||
|
|
||||
|
def describe_token(token): |
||||
|
"""Returns a description of the token.""" |
||||
|
if token.type == 'name': |
||||
|
return token.value |
||||
|
return _describe_token_type(token.type) |
||||
|
|
||||
|
|
||||
|
def describe_token_expr(expr): |
||||
|
"""Like `describe_token` but for token expressions.""" |
||||
|
if ':' in expr: |
||||
|
type, value = expr.split(':', 1) |
||||
|
if type == 'name': |
||||
|
return value |
||||
|
else: |
||||
|
type = expr |
||||
|
return _describe_token_type(type) |
||||
|
|
||||
|
|
||||
|
def count_newlines(value): |
||||
|
"""Count the number of newline characters in the string. This is |
||||
|
useful for extensions that filter a stream. |
||||
|
""" |
||||
|
return len(newline_re.findall(value)) |
||||
|
|
||||
|
|
||||
|
def compile_rules(environment): |
||||
|
"""Compiles all the rules from the environment into a list of rules.""" |
||||
|
e = re.escape |
||||
|
rules = [ |
||||
|
(len(environment.comment_start_string), 'comment', |
||||
|
e(environment.comment_start_string)), |
||||
|
(len(environment.block_start_string), 'block', |
||||
|
e(environment.block_start_string)), |
||||
|
(len(environment.variable_start_string), 'variable', |
||||
|
e(environment.variable_start_string)) |
||||
|
] |
||||
|
|
||||
|
if environment.line_statement_prefix is not None: |
||||
|
rules.append((len(environment.line_statement_prefix), 'linestatement', |
||||
|
r'^[ \t\v]*' + e(environment.line_statement_prefix))) |
||||
|
if environment.line_comment_prefix is not None: |
||||
|
rules.append((len(environment.line_comment_prefix), 'linecomment', |
||||
|
r'(?:^|(?<=\S))[^\S\r\n]*' + |
||||
|
e(environment.line_comment_prefix))) |
||||
|
|
||||
|
return [x[1:] for x in sorted(rules, reverse=True)] |
||||
|
|
||||
|
|
||||
|
class Failure(object): |
||||
|
"""Class that raises a `TemplateSyntaxError` if called. |
||||
|
Used by the `Lexer` to specify known errors. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, message, cls=TemplateSyntaxError): |
||||
|
self.message = message |
||||
|
self.error_class = cls |
||||
|
|
||||
|
def __call__(self, lineno, filename): |
||||
|
raise self.error_class(self.message, lineno, filename) |
||||
|
|
||||
|
|
||||
|
class Token(tuple): |
||||
|
"""Token class.""" |
||||
|
__slots__ = () |
||||
|
lineno, type, value = (property(itemgetter(x)) for x in range(3)) |
||||
|
|
||||
|
def __new__(cls, lineno, type, value): |
||||
|
return tuple.__new__(cls, (lineno, intern(str(type)), value)) |
||||
|
|
||||
|
def __str__(self): |
||||
|
if self.type in reverse_operators: |
||||
|
return reverse_operators[self.type] |
||||
|
elif self.type == 'name': |
||||
|
return self.value |
||||
|
return self.type |
||||
|
|
||||
|
def test(self, expr): |
||||
|
"""Test a token against a token expression. This can either be a |
||||
|
token type or ``'token_type:token_value'``. This can only test |
||||
|
against string values and types. |
||||
|
""" |
||||
|
# here we do a regular string equality check as test_any is usually |
||||
|
# passed an iterable of not interned strings. |
||||
|
if self.type == expr: |
||||
|
return True |
||||
|
elif ':' in expr: |
||||
|
return expr.split(':', 1) == [self.type, self.value] |
||||
|
return False |
||||
|
|
||||
|
def test_any(self, *iterable): |
||||
|
"""Test against multiple token expressions.""" |
||||
|
for expr in iterable: |
||||
|
if self.test(expr): |
||||
|
return True |
||||
|
return False |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return 'Token(%r, %r, %r)' % ( |
||||
|
self.lineno, |
||||
|
self.type, |
||||
|
self.value |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@implements_iterator |
||||
|
class TokenStreamIterator(object): |
||||
|
"""The iterator for tokenstreams. Iterate over the stream |
||||
|
until the eof token is reached. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, stream): |
||||
|
self.stream = stream |
||||
|
|
||||
|
def __iter__(self): |
||||
|
return self |
||||
|
|
||||
|
def __next__(self): |
||||
|
token = self.stream.current |
||||
|
if token.type is TOKEN_EOF: |
||||
|
self.stream.close() |
||||
|
raise StopIteration() |
||||
|
next(self.stream) |
||||
|
return token |
||||
|
|
||||
|
|
||||
|
@implements_iterator |
||||
|
class TokenStream(object): |
||||
|
"""A token stream is an iterable that yields :class:`Token`\s. The |
||||
|
parser however does not iterate over it but calls :meth:`next` to go |
||||
|
one token ahead. The current active token is stored as :attr:`current`. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, generator, name, filename): |
||||
|
self._iter = iter(generator) |
||||
|
self._pushed = deque() |
||||
|
self.name = name |
||||
|
self.filename = filename |
||||
|
self.closed = False |
||||
|
self.current = Token(1, TOKEN_INITIAL, '') |
||||
|
next(self) |
||||
|
|
||||
|
def __iter__(self): |
||||
|
return TokenStreamIterator(self) |
||||
|
|
||||
|
def __bool__(self): |
||||
|
return bool(self._pushed) or self.current.type is not TOKEN_EOF |
||||
|
__nonzero__ = __bool__ # py2 |
||||
|
|
||||
|
eos = property(lambda x: not x, doc="Are we at the end of the stream?") |
||||
|
|
||||
|
def push(self, token): |
||||
|
"""Push a token back to the stream.""" |
||||
|
self._pushed.append(token) |
||||
|
|
||||
|
def look(self): |
||||
|
"""Look at the next token.""" |
||||
|
old_token = next(self) |
||||
|
result = self.current |
||||
|
self.push(result) |
||||
|
self.current = old_token |
||||
|
return result |
||||
|
|
||||
|
def skip(self, n=1): |
||||
|
"""Got n tokens ahead.""" |
||||
|
for x in range(n): |
||||
|
next(self) |
||||
|
|
||||
|
def next_if(self, expr): |
||||
|
"""Perform the token test and return the token if it matched. |
||||
|
Otherwise the return value is `None`. |
||||
|
""" |
||||
|
if self.current.test(expr): |
||||
|
return next(self) |
||||
|
|
||||
|
def skip_if(self, expr): |
||||
|
"""Like :meth:`next_if` but only returns `True` or `False`.""" |
||||
|
return self.next_if(expr) is not None |
||||
|
|
||||
|
def __next__(self): |
||||
|
"""Go one token ahead and return the old one""" |
||||
|
rv = self.current |
||||
|
if self._pushed: |
||||
|
self.current = self._pushed.popleft() |
||||
|
elif self.current.type is not TOKEN_EOF: |
||||
|
try: |
||||
|
self.current = next(self._iter) |
||||
|
except StopIteration: |
||||
|
self.close() |
||||
|
return rv |
||||
|
|
||||
|
def close(self): |
||||
|
"""Close the stream.""" |
||||
|
self.current = Token(self.current.lineno, TOKEN_EOF, '') |
||||
|
self._iter = None |
||||
|
self.closed = True |
||||
|
|
||||
|
def expect(self, expr): |
||||
|
"""Expect a given token type and return it. This accepts the same |
||||
|
argument as :meth:`jinja2.lexer.Token.test`. |
||||
|
""" |
||||
|
if not self.current.test(expr): |
||||
|
expr = describe_token_expr(expr) |
||||
|
if self.current.type is TOKEN_EOF: |
||||
|
raise TemplateSyntaxError('unexpected end of template, ' |
||||
|
'expected %r.' % expr, |
||||
|
self.current.lineno, |
||||
|
self.name, self.filename) |
||||
|
raise TemplateSyntaxError("expected token %r, got %r" % |
||||
|
(expr, describe_token(self.current)), |
||||
|
self.current.lineno, |
||||
|
self.name, self.filename) |
||||
|
try: |
||||
|
return self.current |
||||
|
finally: |
||||
|
next(self) |
||||
|
|
||||
|
|
||||
|
def get_lexer(environment): |
||||
|
"""Return a lexer which is probably cached.""" |
||||
|
key = (environment.block_start_string, |
||||
|
environment.block_end_string, |
||||
|
environment.variable_start_string, |
||||
|
environment.variable_end_string, |
||||
|
environment.comment_start_string, |
||||
|
environment.comment_end_string, |
||||
|
environment.line_statement_prefix, |
||||
|
environment.line_comment_prefix, |
||||
|
environment.trim_blocks, |
||||
|
environment.lstrip_blocks, |
||||
|
environment.newline_sequence, |
||||
|
environment.keep_trailing_newline) |
||||
|
lexer = _lexer_cache.get(key) |
||||
|
if lexer is None: |
||||
|
lexer = Lexer(environment) |
||||
|
_lexer_cache[key] = lexer |
||||
|
return lexer |
||||
|
|
||||
|
|
||||
|
class Lexer(object): |
||||
|
"""Class that implements a lexer for a given environment. Automatically |
||||
|
created by the environment class, usually you don't have to do that. |
||||
|
|
||||
|
Note that the lexer is not automatically bound to an environment. |
||||
|
Multiple environments can share the same lexer. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, environment): |
||||
|
# shortcuts |
||||
|
c = lambda x: re.compile(x, re.M | re.S) |
||||
|
e = re.escape |
||||
|
|
||||
|
# lexing rules for tags |
||||
|
tag_rules = [ |
||||
|
(whitespace_re, TOKEN_WHITESPACE, None), |
||||
|
(float_re, TOKEN_FLOAT, None), |
||||
|
(integer_re, TOKEN_INTEGER, None), |
||||
|
(name_re, TOKEN_NAME, None), |
||||
|
(string_re, TOKEN_STRING, None), |
||||
|
(operator_re, TOKEN_OPERATOR, None) |
||||
|
] |
||||
|
|
||||
|
# assemble the root lexing rule. because "|" is ungreedy |
||||
|
# we have to sort by length so that the lexer continues working |
||||
|
# as expected when we have parsing rules like <% for block and |
||||
|
# <%= for variables. (if someone wants asp like syntax) |
||||
|
# variables are just part of the rules if variable processing |
||||
|
# is required. |
||||
|
root_tag_rules = compile_rules(environment) |
||||
|
|
||||
|
# block suffix if trimming is enabled |
||||
|
block_suffix_re = environment.trim_blocks and '\\n?' or '' |
||||
|
|
||||
|
# strip leading spaces if lstrip_blocks is enabled |
||||
|
prefix_re = {} |
||||
|
if environment.lstrip_blocks: |
||||
|
# use '{%+' to manually disable lstrip_blocks behavior |
||||
|
no_lstrip_re = e('+') |
||||
|
# detect overlap between block and variable or comment strings |
||||
|
block_diff = c(r'^%s(.*)' % e(environment.block_start_string)) |
||||
|
# make sure we don't mistake a block for a variable or a comment |
||||
|
m = block_diff.match(environment.comment_start_string) |
||||
|
no_lstrip_re += m and r'|%s' % e(m.group(1)) or '' |
||||
|
m = block_diff.match(environment.variable_start_string) |
||||
|
no_lstrip_re += m and r'|%s' % e(m.group(1)) or '' |
||||
|
|
||||
|
# detect overlap between comment and variable strings |
||||
|
comment_diff = c(r'^%s(.*)' % e(environment.comment_start_string)) |
||||
|
m = comment_diff.match(environment.variable_start_string) |
||||
|
no_variable_re = m and r'(?!%s)' % e(m.group(1)) or '' |
||||
|
|
||||
|
lstrip_re = r'^[ \t]*' |
||||
|
block_prefix_re = r'%s%s(?!%s)|%s\+?' % ( |
||||
|
lstrip_re, |
||||
|
e(environment.block_start_string), |
||||
|
no_lstrip_re, |
||||
|
e(environment.block_start_string), |
||||
|
) |
||||
|
comment_prefix_re = r'%s%s%s|%s\+?' % ( |
||||
|
lstrip_re, |
||||
|
e(environment.comment_start_string), |
||||
|
no_variable_re, |
||||
|
e(environment.comment_start_string), |
||||
|
) |
||||
|
prefix_re['block'] = block_prefix_re |
||||
|
prefix_re['comment'] = comment_prefix_re |
||||
|
else: |
||||
|
block_prefix_re = '%s' % e(environment.block_start_string) |
||||
|
|
||||
|
self.newline_sequence = environment.newline_sequence |
||||
|
self.keep_trailing_newline = environment.keep_trailing_newline |
||||
|
|
||||
|
# global lexing rules |
||||
|
self.rules = { |
||||
|
'root': [ |
||||
|
# directives |
||||
|
(c('(.*?)(?:%s)' % '|'.join( |
||||
|
[r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % ( |
||||
|
e(environment.block_start_string), |
||||
|
block_prefix_re, |
||||
|
e(environment.block_end_string), |
||||
|
e(environment.block_end_string) |
||||
|
)] + [ |
||||
|
r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, prefix_re.get(n,r)) |
||||
|
for n, r in root_tag_rules |
||||
|
])), (TOKEN_DATA, '#bygroup'), '#bygroup'), |
||||
|
# data |
||||
|
(c('.+'), TOKEN_DATA, None) |
||||
|
], |
||||
|
# comments |
||||
|
TOKEN_COMMENT_BEGIN: [ |
||||
|
(c(r'(.*?)((?:\-%s\s*|%s)%s)' % ( |
||||
|
e(environment.comment_end_string), |
||||
|
e(environment.comment_end_string), |
||||
|
block_suffix_re |
||||
|
)), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'), |
||||
|
(c('(.)'), (Failure('Missing end of comment tag'),), None) |
||||
|
], |
||||
|
# blocks |
||||
|
TOKEN_BLOCK_BEGIN: [ |
||||
|
(c('(?:\-%s\s*|%s)%s' % ( |
||||
|
e(environment.block_end_string), |
||||
|
e(environment.block_end_string), |
||||
|
block_suffix_re |
||||
|
)), TOKEN_BLOCK_END, '#pop'), |
||||
|
] + tag_rules, |
||||
|
# variables |
||||
|
TOKEN_VARIABLE_BEGIN: [ |
||||
|
(c('\-%s\s*|%s' % ( |
||||
|
e(environment.variable_end_string), |
||||
|
e(environment.variable_end_string) |
||||
|
)), TOKEN_VARIABLE_END, '#pop') |
||||
|
] + tag_rules, |
||||
|
# raw block |
||||
|
TOKEN_RAW_BEGIN: [ |
||||
|
(c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % ( |
||||
|
e(environment.block_start_string), |
||||
|
block_prefix_re, |
||||
|
e(environment.block_end_string), |
||||
|
e(environment.block_end_string), |
||||
|
block_suffix_re |
||||
|
)), (TOKEN_DATA, TOKEN_RAW_END), '#pop'), |
||||
|
(c('(.)'), (Failure('Missing end of raw directive'),), None) |
||||
|
], |
||||
|
# line statements |
||||
|
TOKEN_LINESTATEMENT_BEGIN: [ |
||||
|
(c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop') |
||||
|
] + tag_rules, |
||||
|
# line comments |
||||
|
TOKEN_LINECOMMENT_BEGIN: [ |
||||
|
(c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT, |
||||
|
TOKEN_LINECOMMENT_END), '#pop') |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
def _normalize_newlines(self, value): |
||||
|
"""Called for strings and template data to normalize it to unicode.""" |
||||
|
return newline_re.sub(self.newline_sequence, value) |
||||
|
|
||||
|
def tokenize(self, source, name=None, filename=None, state=None): |
||||
|
"""Calls tokeniter + tokenize and wraps it in a token stream. |
||||
|
""" |
||||
|
stream = self.tokeniter(source, name, filename, state) |
||||
|
return TokenStream(self.wrap(stream, name, filename), name, filename) |
||||
|
|
||||
|
def wrap(self, stream, name=None, filename=None): |
||||
|
"""This is called with the stream as returned by `tokenize` and wraps |
||||
|
every token in a :class:`Token` and converts the value. |
||||
|
""" |
||||
|
for lineno, token, value in stream: |
||||
|
if token in ignored_tokens: |
||||
|
continue |
||||
|
elif token == 'linestatement_begin': |
||||
|
token = 'block_begin' |
||||
|
elif token == 'linestatement_end': |
||||
|
token = 'block_end' |
||||
|
# we are not interested in those tokens in the parser |
||||
|
elif token in ('raw_begin', 'raw_end'): |
||||
|
continue |
||||
|
elif token == 'data': |
||||
|
value = self._normalize_newlines(value) |
||||
|
elif token == 'keyword': |
||||
|
token = value |
||||
|
elif token == 'name': |
||||
|
value = str(value) |
||||
|
elif token == 'string': |
||||
|
# try to unescape string |
||||
|
try: |
||||
|
value = self._normalize_newlines(value[1:-1]) \ |
||||
|
.encode('ascii', 'backslashreplace') \ |
||||
|
.decode('unicode-escape') |
||||
|
except Exception as e: |
||||
|
msg = str(e).split(':')[-1].strip() |
||||
|
raise TemplateSyntaxError(msg, lineno, name, filename) |
||||
|
# if we can express it as bytestring (ascii only) |
||||
|
# we do that for support of semi broken APIs |
||||
|
# as datetime.datetime.strftime. On python 3 this |
||||
|
# call becomes a noop thanks to 2to3 |
||||
|
if PY2: |
||||
|
try: |
||||
|
value = value.encode('ascii') |
||||
|
except UnicodeError: |
||||
|
pass |
||||
|
elif token == 'integer': |
||||
|
value = int(value) |
||||
|
elif token == 'float': |
||||
|
value = float(value) |
||||
|
elif token == 'operator': |
||||
|
token = operators[value] |
||||
|
yield Token(lineno, token, value) |
||||
|
|
||||
|
def tokeniter(self, source, name, filename=None, state=None): |
||||
|
"""This method tokenizes the text and returns the tokens in a |
||||
|
generator. Use this method if you just want to tokenize a template. |
||||
|
""" |
||||
|
source = text_type(source) |
||||
|
lines = source.splitlines() |
||||
|
if self.keep_trailing_newline and source: |
||||
|
for newline in ('\r\n', '\r', '\n'): |
||||
|
if source.endswith(newline): |
||||
|
lines.append('') |
||||
|
break |
||||
|
source = '\n'.join(lines) |
||||
|
pos = 0 |
||||
|
lineno = 1 |
||||
|
stack = ['root'] |
||||
|
if state is not None and state != 'root': |
||||
|
assert state in ('variable', 'block'), 'invalid state' |
||||
|
stack.append(state + '_begin') |
||||
|
else: |
||||
|
state = 'root' |
||||
|
statetokens = self.rules[stack[-1]] |
||||
|
source_length = len(source) |
||||
|
|
||||
|
balancing_stack = [] |
||||
|
|
||||
|
while 1: |
||||
|
# tokenizer loop |
||||
|
for regex, tokens, new_state in statetokens: |
||||
|
m = regex.match(source, pos) |
||||
|
# if no match we try again with the next rule |
||||
|
if m is None: |
||||
|
continue |
||||
|
|
||||
|
# we only match blocks and variables if braces / parentheses |
||||
|
# are balanced. continue parsing with the lower rule which |
||||
|
# is the operator rule. do this only if the end tags look |
||||
|
# like operators |
||||
|
if balancing_stack and \ |
||||
|
tokens in ('variable_end', 'block_end', |
||||
|
'linestatement_end'): |
||||
|
continue |
||||
|
|
||||
|
# tuples support more options |
||||
|
if isinstance(tokens, tuple): |
||||
|
for idx, token in enumerate(tokens): |
||||
|
# failure group |
||||
|
if token.__class__ is Failure: |
||||
|
raise token(lineno, filename) |
||||
|
# bygroup is a bit more complex, in that case we |
||||
|
# yield for the current token the first named |
||||
|
# group that matched |
||||
|
elif token == '#bygroup': |
||||
|
for key, value in iteritems(m.groupdict()): |
||||
|
if value is not None: |
||||
|
yield lineno, key, value |
||||
|
lineno += value.count('\n') |
||||
|
break |
||||
|
else: |
||||
|
raise RuntimeError('%r wanted to resolve ' |
||||
|
'the token dynamically' |
||||
|
' but no group matched' |
||||
|
% regex) |
||||
|
# normal group |
||||
|
else: |
||||
|
data = m.group(idx + 1) |
||||
|
if data or token not in ignore_if_empty: |
||||
|
yield lineno, token, data |
||||
|
lineno += data.count('\n') |
||||
|
|
||||
|
# strings as token just are yielded as it. |
||||
|
else: |
||||
|
data = m.group() |
||||
|
# update brace/parentheses balance |
||||
|
if tokens == 'operator': |
||||
|
if data == '{': |
||||
|
balancing_stack.append('}') |
||||
|
elif data == '(': |
||||
|
balancing_stack.append(')') |
||||
|
elif data == '[': |
||||
|
balancing_stack.append(']') |
||||
|
elif data in ('}', ')', ']'): |
||||
|
if not balancing_stack: |
||||
|
raise TemplateSyntaxError('unexpected \'%s\'' % |
||||
|
data, lineno, name, |
||||
|
filename) |
||||
|
expected_op = balancing_stack.pop() |
||||
|
if expected_op != data: |
||||
|
raise TemplateSyntaxError('unexpected \'%s\', ' |
||||
|
'expected \'%s\'' % |
||||
|
(data, expected_op), |
||||
|
lineno, name, |
||||
|
filename) |
||||
|
# yield items |
||||
|
if data or tokens not in ignore_if_empty: |
||||
|
yield lineno, tokens, data |
||||
|
lineno += data.count('\n') |
||||
|
|
||||
|
# fetch new position into new variable so that we can check |
||||
|
# if there is a internal parsing error which would result |
||||
|
# in an infinite loop |
||||
|
pos2 = m.end() |
||||
|
|
||||
|
# handle state changes |
||||
|
if new_state is not None: |
||||
|
# remove the uppermost state |
||||
|
if new_state == '#pop': |
||||
|
stack.pop() |
||||
|
# resolve the new state by group checking |
||||
|
elif new_state == '#bygroup': |
||||
|
for key, value in iteritems(m.groupdict()): |
||||
|
if value is not None: |
||||
|
stack.append(key) |
||||
|
break |
||||
|
else: |
||||
|
raise RuntimeError('%r wanted to resolve the ' |
||||
|
'new state dynamically but' |
||||
|
' no group matched' % |
||||
|
regex) |
||||
|
# direct state name given |
||||
|
else: |
||||
|
stack.append(new_state) |
||||
|
statetokens = self.rules[stack[-1]] |
||||
|
# we are still at the same position and no stack change. |
||||
|
# this means a loop without break condition, avoid that and |
||||
|
# raise error |
||||
|
elif pos2 == pos: |
||||
|
raise RuntimeError('%r yielded empty string without ' |
||||
|
'stack change' % regex) |
||||
|
# publish new function and start again |
||||
|
pos = pos2 |
||||
|
break |
||||
|
# if loop terminated without break we haven't found a single match |
||||
|
# either we are at the end of the file or we have a problem |
||||
|
else: |
||||
|
# end of text |
||||
|
if pos >= source_length: |
||||
|
return |
||||
|
# something went wrong |
||||
|
raise TemplateSyntaxError('unexpected char %r at %d' % |
||||
|
(source[pos], pos), lineno, |
||||
|
name, filename) |
@ -0,0 +1,481 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.loaders |
||||
|
~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Jinja loader classes. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
import os |
||||
|
import sys |
||||
|
import weakref |
||||
|
from types import ModuleType |
||||
|
from os import path |
||||
|
from hashlib import sha1 |
||||
|
from jinja2.exceptions import TemplateNotFound |
||||
|
from jinja2.utils import open_if_exists, internalcode |
||||
|
from jinja2._compat import string_types, iteritems |
||||
|
|
||||
|
|
||||
|
def split_template_path(template): |
||||
|
"""Split a path into segments and perform a sanity check. If it detects |
||||
|
'..' in the path it will raise a `TemplateNotFound` error. |
||||
|
""" |
||||
|
pieces = [] |
||||
|
for piece in template.split('/'): |
||||
|
if path.sep in piece \ |
||||
|
or (path.altsep and path.altsep in piece) or \ |
||||
|
piece == path.pardir: |
||||
|
raise TemplateNotFound(template) |
||||
|
elif piece and piece != '.': |
||||
|
pieces.append(piece) |
||||
|
return pieces |
||||
|
|
||||
|
|
||||
|
class BaseLoader(object): |
||||
|
"""Baseclass for all loaders. Subclass this and override `get_source` to |
||||
|
implement a custom loading mechanism. The environment provides a |
||||
|
`get_template` method that calls the loader's `load` method to get the |
||||
|
:class:`Template` object. |
||||
|
|
||||
|
A very basic example for a loader that looks up templates on the file |
||||
|
system could look like this:: |
||||
|
|
||||
|
from jinja2 import BaseLoader, TemplateNotFound |
||||
|
from os.path import join, exists, getmtime |
||||
|
|
||||
|
class MyLoader(BaseLoader): |
||||
|
|
||||
|
def __init__(self, path): |
||||
|
self.path = path |
||||
|
|
||||
|
def get_source(self, environment, template): |
||||
|
path = join(self.path, template) |
||||
|
if not exists(path): |
||||
|
raise TemplateNotFound(template) |
||||
|
mtime = getmtime(path) |
||||
|
with file(path) as f: |
||||
|
source = f.read().decode('utf-8') |
||||
|
return source, path, lambda: mtime == getmtime(path) |
||||
|
""" |
||||
|
|
||||
|
#: if set to `False` it indicates that the loader cannot provide access |
||||
|
#: to the source of templates. |
||||
|
#: |
||||
|
#: .. versionadded:: 2.4 |
||||
|
has_source_access = True |
||||
|
|
||||
|
def get_source(self, environment, template): |
||||
|
"""Get the template source, filename and reload helper for a template. |
||||
|
It's passed the environment and template name and has to return a |
||||
|
tuple in the form ``(source, filename, uptodate)`` or raise a |
||||
|
`TemplateNotFound` error if it can't locate the template. |
||||
|
|
||||
|
The source part of the returned tuple must be the source of the |
||||
|
template as unicode string or a ASCII bytestring. The filename should |
||||
|
be the name of the file on the filesystem if it was loaded from there, |
||||
|
otherwise `None`. The filename is used by python for the tracebacks |
||||
|
if no loader extension is used. |
||||
|
|
||||
|
The last item in the tuple is the `uptodate` function. If auto |
||||
|
reloading is enabled it's always called to check if the template |
||||
|
changed. No arguments are passed so the function must store the |
||||
|
old state somewhere (for example in a closure). If it returns `False` |
||||
|
the template will be reloaded. |
||||
|
""" |
||||
|
if not self.has_source_access: |
||||
|
raise RuntimeError('%s cannot provide access to the source' % |
||||
|
self.__class__.__name__) |
||||
|
raise TemplateNotFound(template) |
||||
|
|
||||
|
def list_templates(self): |
||||
|
"""Iterates over all templates. If the loader does not support that |
||||
|
it should raise a :exc:`TypeError` which is the default behavior. |
||||
|
""" |
||||
|
raise TypeError('this loader cannot iterate over all templates') |
||||
|
|
||||
|
@internalcode |
||||
|
def load(self, environment, name, globals=None): |
||||
|
"""Loads a template. This method looks up the template in the cache |
||||
|
or loads one by calling :meth:`get_source`. Subclasses should not |
||||
|
override this method as loaders working on collections of other |
||||
|
loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`) |
||||
|
will not call this method but `get_source` directly. |
||||
|
""" |
||||
|
code = None |
||||
|
if globals is None: |
||||
|
globals = {} |
||||
|
|
||||
|
# first we try to get the source for this template together |
||||
|
# with the filename and the uptodate function. |
||||
|
source, filename, uptodate = self.get_source(environment, name) |
||||
|
|
||||
|
# try to load the code from the bytecode cache if there is a |
||||
|
# bytecode cache configured. |
||||
|
bcc = environment.bytecode_cache |
||||
|
if bcc is not None: |
||||
|
bucket = bcc.get_bucket(environment, name, filename, source) |
||||
|
code = bucket.code |
||||
|
|
||||
|
# if we don't have code so far (not cached, no longer up to |
||||
|
# date) etc. we compile the template |
||||
|
if code is None: |
||||
|
code = environment.compile(source, name, filename) |
||||
|
|
||||
|
# if the bytecode cache is available and the bucket doesn't |
||||
|
# have a code so far, we give the bucket the new code and put |
||||
|
# it back to the bytecode cache. |
||||
|
if bcc is not None and bucket.code is None: |
||||
|
bucket.code = code |
||||
|
bcc.set_bucket(bucket) |
||||
|
|
||||
|
return environment.template_class.from_code(environment, code, |
||||
|
globals, uptodate) |
||||
|
|
||||
|
|
||||
|
class FileSystemLoader(BaseLoader): |
||||
|
"""Loads templates from the file system. This loader can find templates |
||||
|
in folders on the file system and is the preferred way to load them. |
||||
|
|
||||
|
The loader takes the path to the templates as string, or if multiple |
||||
|
locations are wanted a list of them which is then looked up in the |
||||
|
given order:: |
||||
|
|
||||
|
>>> loader = FileSystemLoader('/path/to/templates') |
||||
|
>>> loader = FileSystemLoader(['/path/to/templates', '/other/path']) |
||||
|
|
||||
|
Per default the template encoding is ``'utf-8'`` which can be changed |
||||
|
by setting the `encoding` parameter to something else. |
||||
|
|
||||
|
To follow symbolic links, set the *followlinks* parameter to ``True``:: |
||||
|
|
||||
|
>>> loader = FileSystemLoader('/path/to/templates', followlinks=True) |
||||
|
|
||||
|
.. versionchanged:: 2.8+ |
||||
|
The *followlinks* parameter was added. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, searchpath, encoding='utf-8', followlinks=False): |
||||
|
if isinstance(searchpath, string_types): |
||||
|
searchpath = [searchpath] |
||||
|
self.searchpath = list(searchpath) |
||||
|
self.encoding = encoding |
||||
|
self.followlinks = followlinks |
||||
|
|
||||
|
def get_source(self, environment, template): |
||||
|
pieces = split_template_path(template) |
||||
|
for searchpath in self.searchpath: |
||||
|
filename = path.join(searchpath, *pieces) |
||||
|
f = open_if_exists(filename) |
||||
|
if f is None: |
||||
|
continue |
||||
|
try: |
||||
|
contents = f.read().decode(self.encoding) |
||||
|
finally: |
||||
|
f.close() |
||||
|
|
||||
|
mtime = path.getmtime(filename) |
||||
|
|
||||
|
def uptodate(): |
||||
|
try: |
||||
|
return path.getmtime(filename) == mtime |
||||
|
except OSError: |
||||
|
return False |
||||
|
return contents, filename, uptodate |
||||
|
raise TemplateNotFound(template) |
||||
|
|
||||
|
def list_templates(self): |
||||
|
found = set() |
||||
|
for searchpath in self.searchpath: |
||||
|
walk_dir = os.walk(searchpath, followlinks=self.followlinks) |
||||
|
for dirpath, dirnames, filenames in walk_dir: |
||||
|
for filename in filenames: |
||||
|
template = os.path.join(dirpath, filename) \ |
||||
|
[len(searchpath):].strip(os.path.sep) \ |
||||
|
.replace(os.path.sep, '/') |
||||
|
if template[:2] == './': |
||||
|
template = template[2:] |
||||
|
if template not in found: |
||||
|
found.add(template) |
||||
|
return sorted(found) |
||||
|
|
||||
|
|
||||
|
class PackageLoader(BaseLoader): |
||||
|
"""Load templates from python eggs or packages. It is constructed with |
||||
|
the name of the python package and the path to the templates in that |
||||
|
package:: |
||||
|
|
||||
|
loader = PackageLoader('mypackage', 'views') |
||||
|
|
||||
|
If the package path is not given, ``'templates'`` is assumed. |
||||
|
|
||||
|
Per default the template encoding is ``'utf-8'`` which can be changed |
||||
|
by setting the `encoding` parameter to something else. Due to the nature |
||||
|
of eggs it's only possible to reload templates if the package was loaded |
||||
|
from the file system and not a zip file. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, package_name, package_path='templates', |
||||
|
encoding='utf-8'): |
||||
|
from pkg_resources import DefaultProvider, ResourceManager, \ |
||||
|
get_provider |
||||
|
provider = get_provider(package_name) |
||||
|
self.encoding = encoding |
||||
|
self.manager = ResourceManager() |
||||
|
self.filesystem_bound = isinstance(provider, DefaultProvider) |
||||
|
self.provider = provider |
||||
|
self.package_path = package_path |
||||
|
|
||||
|
def get_source(self, environment, template): |
||||
|
pieces = split_template_path(template) |
||||
|
p = '/'.join((self.package_path,) + tuple(pieces)) |
||||
|
if not self.provider.has_resource(p): |
||||
|
raise TemplateNotFound(template) |
||||
|
|
||||
|
filename = uptodate = None |
||||
|
if self.filesystem_bound: |
||||
|
filename = self.provider.get_resource_filename(self.manager, p) |
||||
|
mtime = path.getmtime(filename) |
||||
|
def uptodate(): |
||||
|
try: |
||||
|
return path.getmtime(filename) == mtime |
||||
|
except OSError: |
||||
|
return False |
||||
|
|
||||
|
source = self.provider.get_resource_string(self.manager, p) |
||||
|
return source.decode(self.encoding), filename, uptodate |
||||
|
|
||||
|
def list_templates(self): |
||||
|
path = self.package_path |
||||
|
if path[:2] == './': |
||||
|
path = path[2:] |
||||
|
elif path == '.': |
||||
|
path = '' |
||||
|
offset = len(path) |
||||
|
results = [] |
||||
|
def _walk(path): |
||||
|
for filename in self.provider.resource_listdir(path): |
||||
|
fullname = path + '/' + filename |
||||
|
if self.provider.resource_isdir(fullname): |
||||
|
_walk(fullname) |
||||
|
else: |
||||
|
results.append(fullname[offset:].lstrip('/')) |
||||
|
_walk(path) |
||||
|
results.sort() |
||||
|
return results |
||||
|
|
||||
|
|
||||
|
class DictLoader(BaseLoader): |
||||
|
"""Loads a template from a python dict. It's passed a dict of unicode |
||||
|
strings bound to template names. This loader is useful for unittesting: |
||||
|
|
||||
|
>>> loader = DictLoader({'index.html': 'source here'}) |
||||
|
|
||||
|
Because auto reloading is rarely useful this is disabled per default. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, mapping): |
||||
|
self.mapping = mapping |
||||
|
|
||||
|
def get_source(self, environment, template): |
||||
|
if template in self.mapping: |
||||
|
source = self.mapping[template] |
||||
|
return source, None, lambda: source == self.mapping.get(template) |
||||
|
raise TemplateNotFound(template) |
||||
|
|
||||
|
def list_templates(self): |
||||
|
return sorted(self.mapping) |
||||
|
|
||||
|
|
||||
|
class FunctionLoader(BaseLoader): |
||||
|
"""A loader that is passed a function which does the loading. The |
||||
|
function receives the name of the template and has to return either |
||||
|
an unicode string with the template source, a tuple in the form ``(source, |
||||
|
filename, uptodatefunc)`` or `None` if the template does not exist. |
||||
|
|
||||
|
>>> def load_template(name): |
||||
|
... if name == 'index.html': |
||||
|
... return '...' |
||||
|
... |
||||
|
>>> loader = FunctionLoader(load_template) |
||||
|
|
||||
|
The `uptodatefunc` is a function that is called if autoreload is enabled |
||||
|
and has to return `True` if the template is still up to date. For more |
||||
|
details have a look at :meth:`BaseLoader.get_source` which has the same |
||||
|
return value. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, load_func): |
||||
|
self.load_func = load_func |
||||
|
|
||||
|
def get_source(self, environment, template): |
||||
|
rv = self.load_func(template) |
||||
|
if rv is None: |
||||
|
raise TemplateNotFound(template) |
||||
|
elif isinstance(rv, string_types): |
||||
|
return rv, None, None |
||||
|
return rv |
||||
|
|
||||
|
|
||||
|
class PrefixLoader(BaseLoader): |
||||
|
"""A loader that is passed a dict of loaders where each loader is bound |
||||
|
to a prefix. The prefix is delimited from the template by a slash per |
||||
|
default, which can be changed by setting the `delimiter` argument to |
||||
|
something else:: |
||||
|
|
||||
|
loader = PrefixLoader({ |
||||
|
'app1': PackageLoader('mypackage.app1'), |
||||
|
'app2': PackageLoader('mypackage.app2') |
||||
|
}) |
||||
|
|
||||
|
By loading ``'app1/index.html'`` the file from the app1 package is loaded, |
||||
|
by loading ``'app2/index.html'`` the file from the second. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, mapping, delimiter='/'): |
||||
|
self.mapping = mapping |
||||
|
self.delimiter = delimiter |
||||
|
|
||||
|
def get_loader(self, template): |
||||
|
try: |
||||
|
prefix, name = template.split(self.delimiter, 1) |
||||
|
loader = self.mapping[prefix] |
||||
|
except (ValueError, KeyError): |
||||
|
raise TemplateNotFound(template) |
||||
|
return loader, name |
||||
|
|
||||
|
def get_source(self, environment, template): |
||||
|
loader, name = self.get_loader(template) |
||||
|
try: |
||||
|
return loader.get_source(environment, name) |
||||
|
except TemplateNotFound: |
||||
|
# re-raise the exception with the correct fileame here. |
||||
|
# (the one that includes the prefix) |
||||
|
raise TemplateNotFound(template) |
||||
|
|
||||
|
@internalcode |
||||
|
def load(self, environment, name, globals=None): |
||||
|
loader, local_name = self.get_loader(name) |
||||
|
try: |
||||
|
return loader.load(environment, local_name, globals) |
||||
|
except TemplateNotFound: |
||||
|
# re-raise the exception with the correct fileame here. |
||||
|
# (the one that includes the prefix) |
||||
|
raise TemplateNotFound(name) |
||||
|
|
||||
|
def list_templates(self): |
||||
|
result = [] |
||||
|
for prefix, loader in iteritems(self.mapping): |
||||
|
for template in loader.list_templates(): |
||||
|
result.append(prefix + self.delimiter + template) |
||||
|
return result |
||||
|
|
||||
|
|
||||
|
class ChoiceLoader(BaseLoader): |
||||
|
"""This loader works like the `PrefixLoader` just that no prefix is |
||||
|
specified. If a template could not be found by one loader the next one |
||||
|
is tried. |
||||
|
|
||||
|
>>> loader = ChoiceLoader([ |
||||
|
... FileSystemLoader('/path/to/user/templates'), |
||||
|
... FileSystemLoader('/path/to/system/templates') |
||||
|
... ]) |
||||
|
|
||||
|
This is useful if you want to allow users to override builtin templates |
||||
|
from a different location. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, loaders): |
||||
|
self.loaders = loaders |
||||
|
|
||||
|
def get_source(self, environment, template): |
||||
|
for loader in self.loaders: |
||||
|
try: |
||||
|
return loader.get_source(environment, template) |
||||
|
except TemplateNotFound: |
||||
|
pass |
||||
|
raise TemplateNotFound(template) |
||||
|
|
||||
|
@internalcode |
||||
|
def load(self, environment, name, globals=None): |
||||
|
for loader in self.loaders: |
||||
|
try: |
||||
|
return loader.load(environment, name, globals) |
||||
|
except TemplateNotFound: |
||||
|
pass |
||||
|
raise TemplateNotFound(name) |
||||
|
|
||||
|
def list_templates(self): |
||||
|
found = set() |
||||
|
for loader in self.loaders: |
||||
|
found.update(loader.list_templates()) |
||||
|
return sorted(found) |
||||
|
|
||||
|
|
||||
|
class _TemplateModule(ModuleType): |
||||
|
"""Like a normal module but with support for weak references""" |
||||
|
|
||||
|
|
||||
|
class ModuleLoader(BaseLoader): |
||||
|
"""This loader loads templates from precompiled templates. |
||||
|
|
||||
|
Example usage: |
||||
|
|
||||
|
>>> loader = ChoiceLoader([ |
||||
|
... ModuleLoader('/path/to/compiled/templates'), |
||||
|
... FileSystemLoader('/path/to/templates') |
||||
|
... ]) |
||||
|
|
||||
|
Templates can be precompiled with :meth:`Environment.compile_templates`. |
||||
|
""" |
||||
|
|
||||
|
has_source_access = False |
||||
|
|
||||
|
def __init__(self, path): |
||||
|
package_name = '_jinja2_module_templates_%x' % id(self) |
||||
|
|
||||
|
# create a fake module that looks for the templates in the |
||||
|
# path given. |
||||
|
mod = _TemplateModule(package_name) |
||||
|
if isinstance(path, string_types): |
||||
|
path = [path] |
||||
|
else: |
||||
|
path = list(path) |
||||
|
mod.__path__ = path |
||||
|
|
||||
|
sys.modules[package_name] = weakref.proxy(mod, |
||||
|
lambda x: sys.modules.pop(package_name, None)) |
||||
|
|
||||
|
# the only strong reference, the sys.modules entry is weak |
||||
|
# so that the garbage collector can remove it once the |
||||
|
# loader that created it goes out of business. |
||||
|
self.module = mod |
||||
|
self.package_name = package_name |
||||
|
|
||||
|
@staticmethod |
||||
|
def get_template_key(name): |
||||
|
return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest() |
||||
|
|
||||
|
@staticmethod |
||||
|
def get_module_filename(name): |
||||
|
return ModuleLoader.get_template_key(name) + '.py' |
||||
|
|
||||
|
@internalcode |
||||
|
def load(self, environment, name, globals=None): |
||||
|
key = self.get_template_key(name) |
||||
|
module = '%s.%s' % (self.package_name, key) |
||||
|
mod = getattr(self.module, module, None) |
||||
|
if mod is None: |
||||
|
try: |
||||
|
mod = __import__(module, None, None, ['root']) |
||||
|
except ImportError: |
||||
|
raise TemplateNotFound(name) |
||||
|
|
||||
|
# remove the entry from sys.modules, we only want the attribute |
||||
|
# on the module object we have stored on the loader. |
||||
|
sys.modules.pop(module, None) |
||||
|
|
||||
|
return environment.template_class.from_module_dict( |
||||
|
environment, mod.__dict__, globals) |
@ -0,0 +1,103 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.meta |
||||
|
~~~~~~~~~~~ |
||||
|
|
||||
|
This module implements various functions that exposes information about |
||||
|
templates that might be interesting for various kinds of applications. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team, see AUTHORS for more details. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
from jinja2 import nodes |
||||
|
from jinja2.compiler import CodeGenerator |
||||
|
from jinja2._compat import string_types |
||||
|
|
||||
|
|
||||
|
class TrackingCodeGenerator(CodeGenerator): |
||||
|
"""We abuse the code generator for introspection.""" |
||||
|
|
||||
|
def __init__(self, environment): |
||||
|
CodeGenerator.__init__(self, environment, '<introspection>', |
||||
|
'<introspection>') |
||||
|
self.undeclared_identifiers = set() |
||||
|
|
||||
|
def write(self, x): |
||||
|
"""Don't write.""" |
||||
|
|
||||
|
def pull_locals(self, frame): |
||||
|
"""Remember all undeclared identifiers.""" |
||||
|
self.undeclared_identifiers.update(frame.identifiers.undeclared) |
||||
|
|
||||
|
|
||||
|
def find_undeclared_variables(ast): |
||||
|
"""Returns a set of all variables in the AST that will be looked up from |
||||
|
the context at runtime. Because at compile time it's not known which |
||||
|
variables will be used depending on the path the execution takes at |
||||
|
runtime, all variables are returned. |
||||
|
|
||||
|
>>> from jinja2 import Environment, meta |
||||
|
>>> env = Environment() |
||||
|
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}') |
||||
|
>>> meta.find_undeclared_variables(ast) == set(['bar']) |
||||
|
True |
||||
|
|
||||
|
.. admonition:: Implementation |
||||
|
|
||||
|
Internally the code generator is used for finding undeclared variables. |
||||
|
This is good to know because the code generator might raise a |
||||
|
:exc:`TemplateAssertionError` during compilation and as a matter of |
||||
|
fact this function can currently raise that exception as well. |
||||
|
""" |
||||
|
codegen = TrackingCodeGenerator(ast.environment) |
||||
|
codegen.visit(ast) |
||||
|
return codegen.undeclared_identifiers |
||||
|
|
||||
|
|
||||
|
def find_referenced_templates(ast): |
||||
|
"""Finds all the referenced templates from the AST. This will return an |
||||
|
iterator over all the hardcoded template extensions, inclusions and |
||||
|
imports. If dynamic inheritance or inclusion is used, `None` will be |
||||
|
yielded. |
||||
|
|
||||
|
>>> from jinja2 import Environment, meta |
||||
|
>>> env = Environment() |
||||
|
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}') |
||||
|
>>> list(meta.find_referenced_templates(ast)) |
||||
|
['layout.html', None] |
||||
|
|
||||
|
This function is useful for dependency tracking. For example if you want |
||||
|
to rebuild parts of the website after a layout template has changed. |
||||
|
""" |
||||
|
for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import, |
||||
|
nodes.Include)): |
||||
|
if not isinstance(node.template, nodes.Const): |
||||
|
# a tuple with some non consts in there |
||||
|
if isinstance(node.template, (nodes.Tuple, nodes.List)): |
||||
|
for template_name in node.template.items: |
||||
|
# something const, only yield the strings and ignore |
||||
|
# non-string consts that really just make no sense |
||||
|
if isinstance(template_name, nodes.Const): |
||||
|
if isinstance(template_name.value, string_types): |
||||
|
yield template_name.value |
||||
|
# something dynamic in there |
||||
|
else: |
||||
|
yield None |
||||
|
# something dynamic we don't know about here |
||||
|
else: |
||||
|
yield None |
||||
|
continue |
||||
|
# constant is a basestring, direct template name |
||||
|
if isinstance(node.template.value, string_types): |
||||
|
yield node.template.value |
||||
|
# a tuple or list (latter *should* not happen) made of consts, |
||||
|
# yield the consts that are strings. We could warn here for |
||||
|
# non string values |
||||
|
elif isinstance(node, nodes.Include) and \ |
||||
|
isinstance(node.template.value, (tuple, list)): |
||||
|
for template_name in node.template.value: |
||||
|
if isinstance(template_name, string_types): |
||||
|
yield template_name |
||||
|
# something else we don't care about, we could warn here |
||||
|
else: |
||||
|
yield None |
@ -0,0 +1,919 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.nodes |
||||
|
~~~~~~~~~~~~ |
||||
|
|
||||
|
This module implements additional nodes derived from the ast base node. |
||||
|
|
||||
|
It also provides some node tree helper functions like `in_lineno` and |
||||
|
`get_nodes` used by the parser and translator in order to normalize |
||||
|
python and jinja nodes. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
import types |
||||
|
import operator |
||||
|
|
||||
|
from collections import deque |
||||
|
from jinja2.utils import Markup |
||||
|
from jinja2._compat import izip, with_metaclass, text_type |
||||
|
|
||||
|
|
||||
|
#: the types we support for context functions |
||||
|
_context_function_types = (types.FunctionType, types.MethodType) |
||||
|
|
||||
|
|
||||
|
_binop_to_func = { |
||||
|
'*': operator.mul, |
||||
|
'/': operator.truediv, |
||||
|
'//': operator.floordiv, |
||||
|
'**': operator.pow, |
||||
|
'%': operator.mod, |
||||
|
'+': operator.add, |
||||
|
'-': operator.sub |
||||
|
} |
||||
|
|
||||
|
_uaop_to_func = { |
||||
|
'not': operator.not_, |
||||
|
'+': operator.pos, |
||||
|
'-': operator.neg |
||||
|
} |
||||
|
|
||||
|
_cmpop_to_func = { |
||||
|
'eq': operator.eq, |
||||
|
'ne': operator.ne, |
||||
|
'gt': operator.gt, |
||||
|
'gteq': operator.ge, |
||||
|
'lt': operator.lt, |
||||
|
'lteq': operator.le, |
||||
|
'in': lambda a, b: a in b, |
||||
|
'notin': lambda a, b: a not in b |
||||
|
} |
||||
|
|
||||
|
|
||||
|
class Impossible(Exception): |
||||
|
"""Raised if the node could not perform a requested action.""" |
||||
|
|
||||
|
|
||||
|
class NodeType(type): |
||||
|
"""A metaclass for nodes that handles the field and attribute |
||||
|
inheritance. fields and attributes from the parent class are |
||||
|
automatically forwarded to the child.""" |
||||
|
|
||||
|
def __new__(cls, name, bases, d): |
||||
|
for attr in 'fields', 'attributes': |
||||
|
storage = [] |
||||
|
storage.extend(getattr(bases[0], attr, ())) |
||||
|
storage.extend(d.get(attr, ())) |
||||
|
assert len(bases) == 1, 'multiple inheritance not allowed' |
||||
|
assert len(storage) == len(set(storage)), 'layout conflict' |
||||
|
d[attr] = tuple(storage) |
||||
|
d.setdefault('abstract', False) |
||||
|
return type.__new__(cls, name, bases, d) |
||||
|
|
||||
|
|
||||
|
class EvalContext(object): |
||||
|
"""Holds evaluation time information. Custom attributes can be attached |
||||
|
to it in extensions. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, environment, template_name=None): |
||||
|
self.environment = environment |
||||
|
if callable(environment.autoescape): |
||||
|
self.autoescape = environment.autoescape(template_name) |
||||
|
else: |
||||
|
self.autoescape = environment.autoescape |
||||
|
self.volatile = False |
||||
|
|
||||
|
def save(self): |
||||
|
return self.__dict__.copy() |
||||
|
|
||||
|
def revert(self, old): |
||||
|
self.__dict__.clear() |
||||
|
self.__dict__.update(old) |
||||
|
|
||||
|
|
||||
|
def get_eval_context(node, ctx): |
||||
|
if ctx is None: |
||||
|
if node.environment is None: |
||||
|
raise RuntimeError('if no eval context is passed, the ' |
||||
|
'node must have an attached ' |
||||
|
'environment.') |
||||
|
return EvalContext(node.environment) |
||||
|
return ctx |
||||
|
|
||||
|
|
||||
|
class Node(with_metaclass(NodeType, object)): |
||||
|
"""Baseclass for all Jinja2 nodes. There are a number of nodes available |
||||
|
of different types. There are four major types: |
||||
|
|
||||
|
- :class:`Stmt`: statements |
||||
|
- :class:`Expr`: expressions |
||||
|
- :class:`Helper`: helper nodes |
||||
|
- :class:`Template`: the outermost wrapper node |
||||
|
|
||||
|
All nodes have fields and attributes. Fields may be other nodes, lists, |
||||
|
or arbitrary values. Fields are passed to the constructor as regular |
||||
|
positional arguments, attributes as keyword arguments. Each node has |
||||
|
two attributes: `lineno` (the line number of the node) and `environment`. |
||||
|
The `environment` attribute is set at the end of the parsing process for |
||||
|
all nodes automatically. |
||||
|
""" |
||||
|
fields = () |
||||
|
attributes = ('lineno', 'environment') |
||||
|
abstract = True |
||||
|
|
||||
|
def __init__(self, *fields, **attributes): |
||||
|
if self.abstract: |
||||
|
raise TypeError('abstract nodes are not instanciable') |
||||
|
if fields: |
||||
|
if len(fields) != len(self.fields): |
||||
|
if not self.fields: |
||||
|
raise TypeError('%r takes 0 arguments' % |
||||
|
self.__class__.__name__) |
||||
|
raise TypeError('%r takes 0 or %d argument%s' % ( |
||||
|
self.__class__.__name__, |
||||
|
len(self.fields), |
||||
|
len(self.fields) != 1 and 's' or '' |
||||
|
)) |
||||
|
for name, arg in izip(self.fields, fields): |
||||
|
setattr(self, name, arg) |
||||
|
for attr in self.attributes: |
||||
|
setattr(self, attr, attributes.pop(attr, None)) |
||||
|
if attributes: |
||||
|
raise TypeError('unknown attribute %r' % |
||||
|
next(iter(attributes))) |
||||
|
|
||||
|
def iter_fields(self, exclude=None, only=None): |
||||
|
"""This method iterates over all fields that are defined and yields |
||||
|
``(key, value)`` tuples. Per default all fields are returned, but |
||||
|
it's possible to limit that to some fields by providing the `only` |
||||
|
parameter or to exclude some using the `exclude` parameter. Both |
||||
|
should be sets or tuples of field names. |
||||
|
""" |
||||
|
for name in self.fields: |
||||
|
if (exclude is only is None) or \ |
||||
|
(exclude is not None and name not in exclude) or \ |
||||
|
(only is not None and name in only): |
||||
|
try: |
||||
|
yield name, getattr(self, name) |
||||
|
except AttributeError: |
||||
|
pass |
||||
|
|
||||
|
def iter_child_nodes(self, exclude=None, only=None): |
||||
|
"""Iterates over all direct child nodes of the node. This iterates |
||||
|
over all fields and yields the values of they are nodes. If the value |
||||
|
of a field is a list all the nodes in that list are returned. |
||||
|
""" |
||||
|
for field, item in self.iter_fields(exclude, only): |
||||
|
if isinstance(item, list): |
||||
|
for n in item: |
||||
|
if isinstance(n, Node): |
||||
|
yield n |
||||
|
elif isinstance(item, Node): |
||||
|
yield item |
||||
|
|
||||
|
def find(self, node_type): |
||||
|
"""Find the first node of a given type. If no such node exists the |
||||
|
return value is `None`. |
||||
|
""" |
||||
|
for result in self.find_all(node_type): |
||||
|
return result |
||||
|
|
||||
|
def find_all(self, node_type): |
||||
|
"""Find all the nodes of a given type. If the type is a tuple, |
||||
|
the check is performed for any of the tuple items. |
||||
|
""" |
||||
|
for child in self.iter_child_nodes(): |
||||
|
if isinstance(child, node_type): |
||||
|
yield child |
||||
|
for result in child.find_all(node_type): |
||||
|
yield result |
||||
|
|
||||
|
def set_ctx(self, ctx): |
||||
|
"""Reset the context of a node and all child nodes. Per default the |
||||
|
parser will all generate nodes that have a 'load' context as it's the |
||||
|
most common one. This method is used in the parser to set assignment |
||||
|
targets and other nodes to a store context. |
||||
|
""" |
||||
|
todo = deque([self]) |
||||
|
while todo: |
||||
|
node = todo.popleft() |
||||
|
if 'ctx' in node.fields: |
||||
|
node.ctx = ctx |
||||
|
todo.extend(node.iter_child_nodes()) |
||||
|
return self |
||||
|
|
||||
|
def set_lineno(self, lineno, override=False): |
||||
|
"""Set the line numbers of the node and children.""" |
||||
|
todo = deque([self]) |
||||
|
while todo: |
||||
|
node = todo.popleft() |
||||
|
if 'lineno' in node.attributes: |
||||
|
if node.lineno is None or override: |
||||
|
node.lineno = lineno |
||||
|
todo.extend(node.iter_child_nodes()) |
||||
|
return self |
||||
|
|
||||
|
def set_environment(self, environment): |
||||
|
"""Set the environment for all nodes.""" |
||||
|
todo = deque([self]) |
||||
|
while todo: |
||||
|
node = todo.popleft() |
||||
|
node.environment = environment |
||||
|
todo.extend(node.iter_child_nodes()) |
||||
|
return self |
||||
|
|
||||
|
def __eq__(self, other): |
||||
|
return type(self) is type(other) and \ |
||||
|
tuple(self.iter_fields()) == tuple(other.iter_fields()) |
||||
|
|
||||
|
def __ne__(self, other): |
||||
|
return not self.__eq__(other) |
||||
|
|
||||
|
# Restore Python 2 hashing behavior on Python 3 |
||||
|
__hash__ = object.__hash__ |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '%s(%s)' % ( |
||||
|
self.__class__.__name__, |
||||
|
', '.join('%s=%r' % (arg, getattr(self, arg, None)) for |
||||
|
arg in self.fields) |
||||
|
) |
||||
|
|
||||
|
|
||||
|
class Stmt(Node): |
||||
|
"""Base node for all statements.""" |
||||
|
abstract = True |
||||
|
|
||||
|
|
||||
|
class Helper(Node): |
||||
|
"""Nodes that exist in a specific context only.""" |
||||
|
abstract = True |
||||
|
|
||||
|
|
||||
|
class Template(Node): |
||||
|
"""Node that represents a template. This must be the outermost node that |
||||
|
is passed to the compiler. |
||||
|
""" |
||||
|
fields = ('body',) |
||||
|
|
||||
|
|
||||
|
class Output(Stmt): |
||||
|
"""A node that holds multiple expressions which are then printed out. |
||||
|
This is used both for the `print` statement and the regular template data. |
||||
|
""" |
||||
|
fields = ('nodes',) |
||||
|
|
||||
|
|
||||
|
class Extends(Stmt): |
||||
|
"""Represents an extends statement.""" |
||||
|
fields = ('template',) |
||||
|
|
||||
|
|
||||
|
class For(Stmt): |
||||
|
"""The for loop. `target` is the target for the iteration (usually a |
||||
|
:class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list |
||||
|
of nodes that are used as loop-body, and `else_` a list of nodes for the |
||||
|
`else` block. If no else node exists it has to be an empty list. |
||||
|
|
||||
|
For filtered nodes an expression can be stored as `test`, otherwise `None`. |
||||
|
""" |
||||
|
fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive') |
||||
|
|
||||
|
|
||||
|
class If(Stmt): |
||||
|
"""If `test` is true, `body` is rendered, else `else_`.""" |
||||
|
fields = ('test', 'body', 'else_') |
||||
|
|
||||
|
|
||||
|
class Macro(Stmt): |
||||
|
"""A macro definition. `name` is the name of the macro, `args` a list of |
||||
|
arguments and `defaults` a list of defaults if there are any. `body` is |
||||
|
a list of nodes for the macro body. |
||||
|
""" |
||||
|
fields = ('name', 'args', 'defaults', 'body') |
||||
|
|
||||
|
|
||||
|
class CallBlock(Stmt): |
||||
|
"""Like a macro without a name but a call instead. `call` is called with |
||||
|
the unnamed macro as `caller` argument this node holds. |
||||
|
""" |
||||
|
fields = ('call', 'args', 'defaults', 'body') |
||||
|
|
||||
|
|
||||
|
class FilterBlock(Stmt): |
||||
|
"""Node for filter sections.""" |
||||
|
fields = ('body', 'filter') |
||||
|
|
||||
|
|
||||
|
class Block(Stmt): |
||||
|
"""A node that represents a block.""" |
||||
|
fields = ('name', 'body', 'scoped') |
||||
|
|
||||
|
|
||||
|
class Include(Stmt): |
||||
|
"""A node that represents the include tag.""" |
||||
|
fields = ('template', 'with_context', 'ignore_missing') |
||||
|
|
||||
|
|
||||
|
class Import(Stmt): |
||||
|
"""A node that represents the import tag.""" |
||||
|
fields = ('template', 'target', 'with_context') |
||||
|
|
||||
|
|
||||
|
class FromImport(Stmt): |
||||
|
"""A node that represents the from import tag. It's important to not |
||||
|
pass unsafe names to the name attribute. The compiler translates the |
||||
|
attribute lookups directly into getattr calls and does *not* use the |
||||
|
subscript callback of the interface. As exported variables may not |
||||
|
start with double underscores (which the parser asserts) this is not a |
||||
|
problem for regular Jinja code, but if this node is used in an extension |
||||
|
extra care must be taken. |
||||
|
|
||||
|
The list of names may contain tuples if aliases are wanted. |
||||
|
""" |
||||
|
fields = ('template', 'names', 'with_context') |
||||
|
|
||||
|
|
||||
|
class ExprStmt(Stmt): |
||||
|
"""A statement that evaluates an expression and discards the result.""" |
||||
|
fields = ('node',) |
||||
|
|
||||
|
|
||||
|
class Assign(Stmt): |
||||
|
"""Assigns an expression to a target.""" |
||||
|
fields = ('target', 'node') |
||||
|
|
||||
|
|
||||
|
class AssignBlock(Stmt): |
||||
|
"""Assigns a block to a target.""" |
||||
|
fields = ('target', 'body') |
||||
|
|
||||
|
|
||||
|
class Expr(Node): |
||||
|
"""Baseclass for all expressions.""" |
||||
|
abstract = True |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
"""Return the value of the expression as constant or raise |
||||
|
:exc:`Impossible` if this was not possible. |
||||
|
|
||||
|
An :class:`EvalContext` can be provided, if none is given |
||||
|
a default context is created which requires the nodes to have |
||||
|
an attached environment. |
||||
|
|
||||
|
.. versionchanged:: 2.4 |
||||
|
the `eval_ctx` parameter was added. |
||||
|
""" |
||||
|
raise Impossible() |
||||
|
|
||||
|
def can_assign(self): |
||||
|
"""Check if it's possible to assign something to this node.""" |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
class BinExpr(Expr): |
||||
|
"""Baseclass for all binary expressions.""" |
||||
|
fields = ('left', 'right') |
||||
|
operator = None |
||||
|
abstract = True |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
# intercepted operators cannot be folded at compile time |
||||
|
if self.environment.sandboxed and \ |
||||
|
self.operator in self.environment.intercepted_binops: |
||||
|
raise Impossible() |
||||
|
f = _binop_to_func[self.operator] |
||||
|
try: |
||||
|
return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx)) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
|
||||
|
|
||||
|
class UnaryExpr(Expr): |
||||
|
"""Baseclass for all unary expressions.""" |
||||
|
fields = ('node',) |
||||
|
operator = None |
||||
|
abstract = True |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
# intercepted operators cannot be folded at compile time |
||||
|
if self.environment.sandboxed and \ |
||||
|
self.operator in self.environment.intercepted_unops: |
||||
|
raise Impossible() |
||||
|
f = _uaop_to_func[self.operator] |
||||
|
try: |
||||
|
return f(self.node.as_const(eval_ctx)) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
|
||||
|
|
||||
|
class Name(Expr): |
||||
|
"""Looks up a name or stores a value in a name. |
||||
|
The `ctx` of the node can be one of the following values: |
||||
|
|
||||
|
- `store`: store a value in the name |
||||
|
- `load`: load that name |
||||
|
- `param`: like `store` but if the name was defined as function parameter. |
||||
|
""" |
||||
|
fields = ('name', 'ctx') |
||||
|
|
||||
|
def can_assign(self): |
||||
|
return self.name not in ('true', 'false', 'none', |
||||
|
'True', 'False', 'None') |
||||
|
|
||||
|
|
||||
|
class Literal(Expr): |
||||
|
"""Baseclass for literals.""" |
||||
|
abstract = True |
||||
|
|
||||
|
|
||||
|
class Const(Literal): |
||||
|
"""All constant values. The parser will return this node for simple |
||||
|
constants such as ``42`` or ``"foo"`` but it can be used to store more |
||||
|
complex values such as lists too. Only constants with a safe |
||||
|
representation (objects where ``eval(repr(x)) == x`` is true). |
||||
|
""" |
||||
|
fields = ('value',) |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
return self.value |
||||
|
|
||||
|
@classmethod |
||||
|
def from_untrusted(cls, value, lineno=None, environment=None): |
||||
|
"""Return a const object if the value is representable as |
||||
|
constant value in the generated code, otherwise it will raise |
||||
|
an `Impossible` exception. |
||||
|
""" |
||||
|
from .compiler import has_safe_repr |
||||
|
if not has_safe_repr(value): |
||||
|
raise Impossible() |
||||
|
return cls(value, lineno=lineno, environment=environment) |
||||
|
|
||||
|
|
||||
|
class TemplateData(Literal): |
||||
|
"""A constant template string.""" |
||||
|
fields = ('data',) |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
if eval_ctx.volatile: |
||||
|
raise Impossible() |
||||
|
if eval_ctx.autoescape: |
||||
|
return Markup(self.data) |
||||
|
return self.data |
||||
|
|
||||
|
|
||||
|
class Tuple(Literal): |
||||
|
"""For loop unpacking and some other things like multiple arguments |
||||
|
for subscripts. Like for :class:`Name` `ctx` specifies if the tuple |
||||
|
is used for loading the names or storing. |
||||
|
""" |
||||
|
fields = ('items', 'ctx') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return tuple(x.as_const(eval_ctx) for x in self.items) |
||||
|
|
||||
|
def can_assign(self): |
||||
|
for item in self.items: |
||||
|
if not item.can_assign(): |
||||
|
return False |
||||
|
return True |
||||
|
|
||||
|
|
||||
|
class List(Literal): |
||||
|
"""Any list literal such as ``[1, 2, 3]``""" |
||||
|
fields = ('items',) |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return [x.as_const(eval_ctx) for x in self.items] |
||||
|
|
||||
|
|
||||
|
class Dict(Literal): |
||||
|
"""Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of |
||||
|
:class:`Pair` nodes. |
||||
|
""" |
||||
|
fields = ('items',) |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return dict(x.as_const(eval_ctx) for x in self.items) |
||||
|
|
||||
|
|
||||
|
class Pair(Helper): |
||||
|
"""A key, value pair for dicts.""" |
||||
|
fields = ('key', 'value') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx) |
||||
|
|
||||
|
|
||||
|
class Keyword(Helper): |
||||
|
"""A key, value pair for keyword arguments where key is a string.""" |
||||
|
fields = ('key', 'value') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return self.key, self.value.as_const(eval_ctx) |
||||
|
|
||||
|
|
||||
|
class CondExpr(Expr): |
||||
|
"""A conditional expression (inline if expression). (``{{ |
||||
|
foo if bar else baz }}``) |
||||
|
""" |
||||
|
fields = ('test', 'expr1', 'expr2') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
if self.test.as_const(eval_ctx): |
||||
|
return self.expr1.as_const(eval_ctx) |
||||
|
|
||||
|
# if we evaluate to an undefined object, we better do that at runtime |
||||
|
if self.expr2 is None: |
||||
|
raise Impossible() |
||||
|
|
||||
|
return self.expr2.as_const(eval_ctx) |
||||
|
|
||||
|
|
||||
|
class Filter(Expr): |
||||
|
"""This node applies a filter on an expression. `name` is the name of |
||||
|
the filter, the rest of the fields are the same as for :class:`Call`. |
||||
|
|
||||
|
If the `node` of a filter is `None` the contents of the last buffer are |
||||
|
filtered. Buffers are created by macros and filter blocks. |
||||
|
""" |
||||
|
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
if eval_ctx.volatile or self.node is None: |
||||
|
raise Impossible() |
||||
|
# we have to be careful here because we call filter_ below. |
||||
|
# if this variable would be called filter, 2to3 would wrap the |
||||
|
# call in a list beause it is assuming we are talking about the |
||||
|
# builtin filter function here which no longer returns a list in |
||||
|
# python 3. because of that, do not rename filter_ to filter! |
||||
|
filter_ = self.environment.filters.get(self.name) |
||||
|
if filter_ is None or getattr(filter_, 'contextfilter', False): |
||||
|
raise Impossible() |
||||
|
obj = self.node.as_const(eval_ctx) |
||||
|
args = [x.as_const(eval_ctx) for x in self.args] |
||||
|
if getattr(filter_, 'evalcontextfilter', False): |
||||
|
args.insert(0, eval_ctx) |
||||
|
elif getattr(filter_, 'environmentfilter', False): |
||||
|
args.insert(0, self.environment) |
||||
|
kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs) |
||||
|
if self.dyn_args is not None: |
||||
|
try: |
||||
|
args.extend(self.dyn_args.as_const(eval_ctx)) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
if self.dyn_kwargs is not None: |
||||
|
try: |
||||
|
kwargs.update(self.dyn_kwargs.as_const(eval_ctx)) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
try: |
||||
|
return filter_(obj, *args, **kwargs) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
|
||||
|
|
||||
|
class Test(Expr): |
||||
|
"""Applies a test on an expression. `name` is the name of the test, the |
||||
|
rest of the fields are the same as for :class:`Call`. |
||||
|
""" |
||||
|
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') |
||||
|
|
||||
|
|
||||
|
class Call(Expr): |
||||
|
"""Calls an expression. `args` is a list of arguments, `kwargs` a list |
||||
|
of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args` |
||||
|
and `dyn_kwargs` has to be either `None` or a node that is used as |
||||
|
node for dynamic positional (``*args``) or keyword (``**kwargs``) |
||||
|
arguments. |
||||
|
""" |
||||
|
fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
if eval_ctx.volatile: |
||||
|
raise Impossible() |
||||
|
obj = self.node.as_const(eval_ctx) |
||||
|
|
||||
|
# don't evaluate context functions |
||||
|
args = [x.as_const(eval_ctx) for x in self.args] |
||||
|
if isinstance(obj, _context_function_types): |
||||
|
if getattr(obj, 'contextfunction', False): |
||||
|
raise Impossible() |
||||
|
elif getattr(obj, 'evalcontextfunction', False): |
||||
|
args.insert(0, eval_ctx) |
||||
|
elif getattr(obj, 'environmentfunction', False): |
||||
|
args.insert(0, self.environment) |
||||
|
|
||||
|
kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs) |
||||
|
if self.dyn_args is not None: |
||||
|
try: |
||||
|
args.extend(self.dyn_args.as_const(eval_ctx)) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
if self.dyn_kwargs is not None: |
||||
|
try: |
||||
|
kwargs.update(self.dyn_kwargs.as_const(eval_ctx)) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
try: |
||||
|
return obj(*args, **kwargs) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
|
||||
|
|
||||
|
class Getitem(Expr): |
||||
|
"""Get an attribute or item from an expression and prefer the item.""" |
||||
|
fields = ('node', 'arg', 'ctx') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
if self.ctx != 'load': |
||||
|
raise Impossible() |
||||
|
try: |
||||
|
return self.environment.getitem(self.node.as_const(eval_ctx), |
||||
|
self.arg.as_const(eval_ctx)) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
|
||||
|
def can_assign(self): |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
class Getattr(Expr): |
||||
|
"""Get an attribute or item from an expression that is a ascii-only |
||||
|
bytestring and prefer the attribute. |
||||
|
""" |
||||
|
fields = ('node', 'attr', 'ctx') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
if self.ctx != 'load': |
||||
|
raise Impossible() |
||||
|
try: |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return self.environment.getattr(self.node.as_const(eval_ctx), |
||||
|
self.attr) |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
|
||||
|
def can_assign(self): |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
class Slice(Expr): |
||||
|
"""Represents a slice object. This must only be used as argument for |
||||
|
:class:`Subscript`. |
||||
|
""" |
||||
|
fields = ('start', 'stop', 'step') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
def const(obj): |
||||
|
if obj is None: |
||||
|
return None |
||||
|
return obj.as_const(eval_ctx) |
||||
|
return slice(const(self.start), const(self.stop), const(self.step)) |
||||
|
|
||||
|
|
||||
|
class Concat(Expr): |
||||
|
"""Concatenates the list of expressions provided after converting them to |
||||
|
unicode. |
||||
|
""" |
||||
|
fields = ('nodes',) |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return ''.join(text_type(x.as_const(eval_ctx)) for x in self.nodes) |
||||
|
|
||||
|
|
||||
|
class Compare(Expr): |
||||
|
"""Compares an expression with some other expressions. `ops` must be a |
||||
|
list of :class:`Operand`\s. |
||||
|
""" |
||||
|
fields = ('expr', 'ops') |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
result = value = self.expr.as_const(eval_ctx) |
||||
|
try: |
||||
|
for op in self.ops: |
||||
|
new_value = op.expr.as_const(eval_ctx) |
||||
|
result = _cmpop_to_func[op.op](value, new_value) |
||||
|
value = new_value |
||||
|
except Exception: |
||||
|
raise Impossible() |
||||
|
return result |
||||
|
|
||||
|
|
||||
|
class Operand(Helper): |
||||
|
"""Holds an operator and an expression.""" |
||||
|
fields = ('op', 'expr') |
||||
|
|
||||
|
if __debug__: |
||||
|
Operand.__doc__ += '\nThe following operators are available: ' + \ |
||||
|
', '.join(sorted('``%s``' % x for x in set(_binop_to_func) | |
||||
|
set(_uaop_to_func) | set(_cmpop_to_func))) |
||||
|
|
||||
|
|
||||
|
class Mul(BinExpr): |
||||
|
"""Multiplies the left with the right node.""" |
||||
|
operator = '*' |
||||
|
|
||||
|
|
||||
|
class Div(BinExpr): |
||||
|
"""Divides the left by the right node.""" |
||||
|
operator = '/' |
||||
|
|
||||
|
|
||||
|
class FloorDiv(BinExpr): |
||||
|
"""Divides the left by the right node and truncates conver the |
||||
|
result into an integer by truncating. |
||||
|
""" |
||||
|
operator = '//' |
||||
|
|
||||
|
|
||||
|
class Add(BinExpr): |
||||
|
"""Add the left to the right node.""" |
||||
|
operator = '+' |
||||
|
|
||||
|
|
||||
|
class Sub(BinExpr): |
||||
|
"""Subtract the right from the left node.""" |
||||
|
operator = '-' |
||||
|
|
||||
|
|
||||
|
class Mod(BinExpr): |
||||
|
"""Left modulo right.""" |
||||
|
operator = '%' |
||||
|
|
||||
|
|
||||
|
class Pow(BinExpr): |
||||
|
"""Left to the power of right.""" |
||||
|
operator = '**' |
||||
|
|
||||
|
|
||||
|
class And(BinExpr): |
||||
|
"""Short circuited AND.""" |
||||
|
operator = 'and' |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx) |
||||
|
|
||||
|
|
||||
|
class Or(BinExpr): |
||||
|
"""Short circuited OR.""" |
||||
|
operator = 'or' |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx) |
||||
|
|
||||
|
|
||||
|
class Not(UnaryExpr): |
||||
|
"""Negate the expression.""" |
||||
|
operator = 'not' |
||||
|
|
||||
|
|
||||
|
class Neg(UnaryExpr): |
||||
|
"""Make the expression negative.""" |
||||
|
operator = '-' |
||||
|
|
||||
|
|
||||
|
class Pos(UnaryExpr): |
||||
|
"""Make the expression positive (noop for most expressions)""" |
||||
|
operator = '+' |
||||
|
|
||||
|
|
||||
|
# Helpers for extensions |
||||
|
|
||||
|
|
||||
|
class EnvironmentAttribute(Expr): |
||||
|
"""Loads an attribute from the environment object. This is useful for |
||||
|
extensions that want to call a callback stored on the environment. |
||||
|
""" |
||||
|
fields = ('name',) |
||||
|
|
||||
|
|
||||
|
class ExtensionAttribute(Expr): |
||||
|
"""Returns the attribute of an extension bound to the environment. |
||||
|
The identifier is the identifier of the :class:`Extension`. |
||||
|
|
||||
|
This node is usually constructed by calling the |
||||
|
:meth:`~jinja2.ext.Extension.attr` method on an extension. |
||||
|
""" |
||||
|
fields = ('identifier', 'name') |
||||
|
|
||||
|
|
||||
|
class ImportedName(Expr): |
||||
|
"""If created with an import name the import name is returned on node |
||||
|
access. For example ``ImportedName('cgi.escape')`` returns the `escape` |
||||
|
function from the cgi module on evaluation. Imports are optimized by the |
||||
|
compiler so there is no need to assign them to local variables. |
||||
|
""" |
||||
|
fields = ('importname',) |
||||
|
|
||||
|
|
||||
|
class InternalName(Expr): |
||||
|
"""An internal name in the compiler. You cannot create these nodes |
||||
|
yourself but the parser provides a |
||||
|
:meth:`~jinja2.parser.Parser.free_identifier` method that creates |
||||
|
a new identifier for you. This identifier is not available from the |
||||
|
template and is not threated specially by the compiler. |
||||
|
""" |
||||
|
fields = ('name',) |
||||
|
|
||||
|
def __init__(self): |
||||
|
raise TypeError('Can\'t create internal names. Use the ' |
||||
|
'`free_identifier` method on a parser.') |
||||
|
|
||||
|
|
||||
|
class MarkSafe(Expr): |
||||
|
"""Mark the wrapped expression as safe (wrap it as `Markup`).""" |
||||
|
fields = ('expr',) |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
return Markup(self.expr.as_const(eval_ctx)) |
||||
|
|
||||
|
|
||||
|
class MarkSafeIfAutoescape(Expr): |
||||
|
"""Mark the wrapped expression as safe (wrap it as `Markup`) but |
||||
|
only if autoescaping is active. |
||||
|
|
||||
|
.. versionadded:: 2.5 |
||||
|
""" |
||||
|
fields = ('expr',) |
||||
|
|
||||
|
def as_const(self, eval_ctx=None): |
||||
|
eval_ctx = get_eval_context(self, eval_ctx) |
||||
|
if eval_ctx.volatile: |
||||
|
raise Impossible() |
||||
|
expr = self.expr.as_const(eval_ctx) |
||||
|
if eval_ctx.autoescape: |
||||
|
return Markup(expr) |
||||
|
return expr |
||||
|
|
||||
|
|
||||
|
class ContextReference(Expr): |
||||
|
"""Returns the current template context. It can be used like a |
||||
|
:class:`Name` node, with a ``'load'`` ctx and will return the |
||||
|
current :class:`~jinja2.runtime.Context` object. |
||||
|
|
||||
|
Here an example that assigns the current template name to a |
||||
|
variable named `foo`:: |
||||
|
|
||||
|
Assign(Name('foo', ctx='store'), |
||||
|
Getattr(ContextReference(), 'name')) |
||||
|
""" |
||||
|
|
||||
|
|
||||
|
class Continue(Stmt): |
||||
|
"""Continue a loop.""" |
||||
|
|
||||
|
|
||||
|
class Break(Stmt): |
||||
|
"""Break a loop.""" |
||||
|
|
||||
|
|
||||
|
class Scope(Stmt): |
||||
|
"""An artificial scope.""" |
||||
|
fields = ('body',) |
||||
|
|
||||
|
|
||||
|
class EvalContextModifier(Stmt): |
||||
|
"""Modifies the eval context. For each option that should be modified, |
||||
|
a :class:`Keyword` has to be added to the :attr:`options` list. |
||||
|
|
||||
|
Example to change the `autoescape` setting:: |
||||
|
|
||||
|
EvalContextModifier(options=[Keyword('autoescape', Const(True))]) |
||||
|
""" |
||||
|
fields = ('options',) |
||||
|
|
||||
|
|
||||
|
class ScopedEvalContextModifier(EvalContextModifier): |
||||
|
"""Modifies the eval context and reverts it later. Works exactly like |
||||
|
:class:`EvalContextModifier` but will only modify the |
||||
|
:class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`. |
||||
|
""" |
||||
|
fields = ('body',) |
||||
|
|
||||
|
|
||||
|
# make sure nobody creates custom nodes |
||||
|
def _failing_new(*args, **kwargs): |
||||
|
raise TypeError('can\'t create custom node types') |
||||
|
NodeType.__new__ = staticmethod(_failing_new); del _failing_new |
@ -0,0 +1,68 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.optimizer |
||||
|
~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
The jinja optimizer is currently trying to constant fold a few expressions |
||||
|
and modify the AST in place so that it should be easier to evaluate it. |
||||
|
|
||||
|
Because the AST does not contain all the scoping information and the |
||||
|
compiler has to find that out, we cannot do all the optimizations we |
||||
|
want. For example loop unrolling doesn't work because unrolled loops would |
||||
|
have a different scoping. |
||||
|
|
||||
|
The solution would be a second syntax tree that has the scoping rules stored. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
from jinja2 import nodes |
||||
|
from jinja2.visitor import NodeTransformer |
||||
|
|
||||
|
|
||||
|
def optimize(node, environment): |
||||
|
"""The context hint can be used to perform an static optimization |
||||
|
based on the context given.""" |
||||
|
optimizer = Optimizer(environment) |
||||
|
return optimizer.visit(node) |
||||
|
|
||||
|
|
||||
|
class Optimizer(NodeTransformer): |
||||
|
|
||||
|
def __init__(self, environment): |
||||
|
self.environment = environment |
||||
|
|
||||
|
def visit_If(self, node): |
||||
|
"""Eliminate dead code.""" |
||||
|
# do not optimize ifs that have a block inside so that it doesn't |
||||
|
# break super(). |
||||
|
if node.find(nodes.Block) is not None: |
||||
|
return self.generic_visit(node) |
||||
|
try: |
||||
|
val = self.visit(node.test).as_const() |
||||
|
except nodes.Impossible: |
||||
|
return self.generic_visit(node) |
||||
|
if val: |
||||
|
body = node.body |
||||
|
else: |
||||
|
body = node.else_ |
||||
|
result = [] |
||||
|
for node in body: |
||||
|
result.extend(self.visit_list(node)) |
||||
|
return result |
||||
|
|
||||
|
def fold(self, node): |
||||
|
"""Do constant folding.""" |
||||
|
node = self.generic_visit(node) |
||||
|
try: |
||||
|
return nodes.Const.from_untrusted(node.as_const(), |
||||
|
lineno=node.lineno, |
||||
|
environment=self.environment) |
||||
|
except nodes.Impossible: |
||||
|
return node |
||||
|
|
||||
|
visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \ |
||||
|
visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \ |
||||
|
visit_Not = visit_Compare = visit_Getitem = visit_Getattr = visit_Call = \ |
||||
|
visit_Filter = visit_Test = visit_CondExpr = fold |
||||
|
del fold |
@ -0,0 +1,899 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.parser |
||||
|
~~~~~~~~~~~~~ |
||||
|
|
||||
|
Implements the template parser. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
from jinja2 import nodes |
||||
|
from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError |
||||
|
from jinja2.lexer import describe_token, describe_token_expr |
||||
|
from jinja2._compat import imap |
||||
|
|
||||
|
|
||||
|
_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print', |
||||
|
'macro', 'include', 'from', 'import', |
||||
|
'set']) |
||||
|
_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq']) |
||||
|
|
||||
|
|
||||
|
class Parser(object): |
||||
|
"""This is the central parsing class Jinja2 uses. It's passed to |
||||
|
extensions and can be used to parse expressions or statements. |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, environment, source, name=None, filename=None, |
||||
|
state=None): |
||||
|
self.environment = environment |
||||
|
self.stream = environment._tokenize(source, name, filename, state) |
||||
|
self.name = name |
||||
|
self.filename = filename |
||||
|
self.closed = False |
||||
|
self.extensions = {} |
||||
|
for extension in environment.iter_extensions(): |
||||
|
for tag in extension.tags: |
||||
|
self.extensions[tag] = extension.parse |
||||
|
self._last_identifier = 0 |
||||
|
self._tag_stack = [] |
||||
|
self._end_token_stack = [] |
||||
|
|
||||
|
def fail(self, msg, lineno=None, exc=TemplateSyntaxError): |
||||
|
"""Convenience method that raises `exc` with the message, passed |
||||
|
line number or last line number as well as the current name and |
||||
|
filename. |
||||
|
""" |
||||
|
if lineno is None: |
||||
|
lineno = self.stream.current.lineno |
||||
|
raise exc(msg, lineno, self.name, self.filename) |
||||
|
|
||||
|
def _fail_ut_eof(self, name, end_token_stack, lineno): |
||||
|
expected = [] |
||||
|
for exprs in end_token_stack: |
||||
|
expected.extend(imap(describe_token_expr, exprs)) |
||||
|
if end_token_stack: |
||||
|
currently_looking = ' or '.join( |
||||
|
"'%s'" % describe_token_expr(expr) |
||||
|
for expr in end_token_stack[-1]) |
||||
|
else: |
||||
|
currently_looking = None |
||||
|
|
||||
|
if name is None: |
||||
|
message = ['Unexpected end of template.'] |
||||
|
else: |
||||
|
message = ['Encountered unknown tag \'%s\'.' % name] |
||||
|
|
||||
|
if currently_looking: |
||||
|
if name is not None and name in expected: |
||||
|
message.append('You probably made a nesting mistake. Jinja ' |
||||
|
'is expecting this tag, but currently looking ' |
||||
|
'for %s.' % currently_looking) |
||||
|
else: |
||||
|
message.append('Jinja was looking for the following tags: ' |
||||
|
'%s.' % currently_looking) |
||||
|
|
||||
|
if self._tag_stack: |
||||
|
message.append('The innermost block that needs to be ' |
||||
|
'closed is \'%s\'.' % self._tag_stack[-1]) |
||||
|
|
||||
|
self.fail(' '.join(message), lineno) |
||||
|
|
||||
|
def fail_unknown_tag(self, name, lineno=None): |
||||
|
"""Called if the parser encounters an unknown tag. Tries to fail |
||||
|
with a human readable error message that could help to identify |
||||
|
the problem. |
||||
|
""" |
||||
|
return self._fail_ut_eof(name, self._end_token_stack, lineno) |
||||
|
|
||||
|
def fail_eof(self, end_tokens=None, lineno=None): |
||||
|
"""Like fail_unknown_tag but for end of template situations.""" |
||||
|
stack = list(self._end_token_stack) |
||||
|
if end_tokens is not None: |
||||
|
stack.append(end_tokens) |
||||
|
return self._fail_ut_eof(None, stack, lineno) |
||||
|
|
||||
|
def is_tuple_end(self, extra_end_rules=None): |
||||
|
"""Are we at the end of a tuple?""" |
||||
|
if self.stream.current.type in ('variable_end', 'block_end', 'rparen'): |
||||
|
return True |
||||
|
elif extra_end_rules is not None: |
||||
|
return self.stream.current.test_any(extra_end_rules) |
||||
|
return False |
||||
|
|
||||
|
def free_identifier(self, lineno=None): |
||||
|
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`.""" |
||||
|
self._last_identifier += 1 |
||||
|
rv = object.__new__(nodes.InternalName) |
||||
|
nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno) |
||||
|
return rv |
||||
|
|
||||
|
def parse_statement(self): |
||||
|
"""Parse a single statement.""" |
||||
|
token = self.stream.current |
||||
|
if token.type != 'name': |
||||
|
self.fail('tag name expected', token.lineno) |
||||
|
self._tag_stack.append(token.value) |
||||
|
pop_tag = True |
||||
|
try: |
||||
|
if token.value in _statement_keywords: |
||||
|
return getattr(self, 'parse_' + self.stream.current.value)() |
||||
|
if token.value == 'call': |
||||
|
return self.parse_call_block() |
||||
|
if token.value == 'filter': |
||||
|
return self.parse_filter_block() |
||||
|
ext = self.extensions.get(token.value) |
||||
|
if ext is not None: |
||||
|
return ext(self) |
||||
|
|
||||
|
# did not work out, remove the token we pushed by accident |
||||
|
# from the stack so that the unknown tag fail function can |
||||
|
# produce a proper error message. |
||||
|
self._tag_stack.pop() |
||||
|
pop_tag = False |
||||
|
self.fail_unknown_tag(token.value, token.lineno) |
||||
|
finally: |
||||
|
if pop_tag: |
||||
|
self._tag_stack.pop() |
||||
|
|
||||
|
def parse_statements(self, end_tokens, drop_needle=False): |
||||
|
"""Parse multiple statements into a list until one of the end tokens |
||||
|
is reached. This is used to parse the body of statements as it also |
||||
|
parses template data if appropriate. The parser checks first if the |
||||
|
current token is a colon and skips it if there is one. Then it checks |
||||
|
for the block end and parses until if one of the `end_tokens` is |
||||
|
reached. Per default the active token in the stream at the end of |
||||
|
the call is the matched end token. If this is not wanted `drop_needle` |
||||
|
can be set to `True` and the end token is removed. |
||||
|
""" |
||||
|
# the first token may be a colon for python compatibility |
||||
|
self.stream.skip_if('colon') |
||||
|
|
||||
|
# in the future it would be possible to add whole code sections |
||||
|
# by adding some sort of end of statement token and parsing those here. |
||||
|
self.stream.expect('block_end') |
||||
|
result = self.subparse(end_tokens) |
||||
|
|
||||
|
# we reached the end of the template too early, the subparser |
||||
|
# does not check for this, so we do that now |
||||
|
if self.stream.current.type == 'eof': |
||||
|
self.fail_eof(end_tokens) |
||||
|
|
||||
|
if drop_needle: |
||||
|
next(self.stream) |
||||
|
return result |
||||
|
|
||||
|
def parse_set(self): |
||||
|
"""Parse an assign statement.""" |
||||
|
lineno = next(self.stream).lineno |
||||
|
target = self.parse_assign_target() |
||||
|
if self.stream.skip_if('assign'): |
||||
|
expr = self.parse_tuple() |
||||
|
return nodes.Assign(target, expr, lineno=lineno) |
||||
|
body = self.parse_statements(('name:endset',), |
||||
|
drop_needle=True) |
||||
|
return nodes.AssignBlock(target, body, lineno=lineno) |
||||
|
|
||||
|
def parse_for(self): |
||||
|
"""Parse a for loop.""" |
||||
|
lineno = self.stream.expect('name:for').lineno |
||||
|
target = self.parse_assign_target(extra_end_rules=('name:in',)) |
||||
|
self.stream.expect('name:in') |
||||
|
iter = self.parse_tuple(with_condexpr=False, |
||||
|
extra_end_rules=('name:recursive',)) |
||||
|
test = None |
||||
|
if self.stream.skip_if('name:if'): |
||||
|
test = self.parse_expression() |
||||
|
recursive = self.stream.skip_if('name:recursive') |
||||
|
body = self.parse_statements(('name:endfor', 'name:else')) |
||||
|
if next(self.stream).value == 'endfor': |
||||
|
else_ = [] |
||||
|
else: |
||||
|
else_ = self.parse_statements(('name:endfor',), drop_needle=True) |
||||
|
return nodes.For(target, iter, body, else_, test, |
||||
|
recursive, lineno=lineno) |
||||
|
|
||||
|
def parse_if(self): |
||||
|
"""Parse an if construct.""" |
||||
|
node = result = nodes.If(lineno=self.stream.expect('name:if').lineno) |
||||
|
while 1: |
||||
|
node.test = self.parse_tuple(with_condexpr=False) |
||||
|
node.body = self.parse_statements(('name:elif', 'name:else', |
||||
|
'name:endif')) |
||||
|
token = next(self.stream) |
||||
|
if token.test('name:elif'): |
||||
|
new_node = nodes.If(lineno=self.stream.current.lineno) |
||||
|
node.else_ = [new_node] |
||||
|
node = new_node |
||||
|
continue |
||||
|
elif token.test('name:else'): |
||||
|
node.else_ = self.parse_statements(('name:endif',), |
||||
|
drop_needle=True) |
||||
|
else: |
||||
|
node.else_ = [] |
||||
|
break |
||||
|
return result |
||||
|
|
||||
|
def parse_block(self): |
||||
|
node = nodes.Block(lineno=next(self.stream).lineno) |
||||
|
node.name = self.stream.expect('name').value |
||||
|
node.scoped = self.stream.skip_if('name:scoped') |
||||
|
|
||||
|
# common problem people encounter when switching from django |
||||
|
# to jinja. we do not support hyphens in block names, so let's |
||||
|
# raise a nicer error message in that case. |
||||
|
if self.stream.current.type == 'sub': |
||||
|
self.fail('Block names in Jinja have to be valid Python ' |
||||
|
'identifiers and may not contain hyphens, use an ' |
||||
|
'underscore instead.') |
||||
|
|
||||
|
node.body = self.parse_statements(('name:endblock',), drop_needle=True) |
||||
|
self.stream.skip_if('name:' + node.name) |
||||
|
return node |
||||
|
|
||||
|
def parse_extends(self): |
||||
|
node = nodes.Extends(lineno=next(self.stream).lineno) |
||||
|
node.template = self.parse_expression() |
||||
|
return node |
||||
|
|
||||
|
def parse_import_context(self, node, default): |
||||
|
if self.stream.current.test_any('name:with', 'name:without') and \ |
||||
|
self.stream.look().test('name:context'): |
||||
|
node.with_context = next(self.stream).value == 'with' |
||||
|
self.stream.skip() |
||||
|
else: |
||||
|
node.with_context = default |
||||
|
return node |
||||
|
|
||||
|
def parse_include(self): |
||||
|
node = nodes.Include(lineno=next(self.stream).lineno) |
||||
|
node.template = self.parse_expression() |
||||
|
if self.stream.current.test('name:ignore') and \ |
||||
|
self.stream.look().test('name:missing'): |
||||
|
node.ignore_missing = True |
||||
|
self.stream.skip(2) |
||||
|
else: |
||||
|
node.ignore_missing = False |
||||
|
return self.parse_import_context(node, True) |
||||
|
|
||||
|
def parse_import(self): |
||||
|
node = nodes.Import(lineno=next(self.stream).lineno) |
||||
|
node.template = self.parse_expression() |
||||
|
self.stream.expect('name:as') |
||||
|
node.target = self.parse_assign_target(name_only=True).name |
||||
|
return self.parse_import_context(node, False) |
||||
|
|
||||
|
def parse_from(self): |
||||
|
node = nodes.FromImport(lineno=next(self.stream).lineno) |
||||
|
node.template = self.parse_expression() |
||||
|
self.stream.expect('name:import') |
||||
|
node.names = [] |
||||
|
|
||||
|
def parse_context(): |
||||
|
if self.stream.current.value in ('with', 'without') and \ |
||||
|
self.stream.look().test('name:context'): |
||||
|
node.with_context = next(self.stream).value == 'with' |
||||
|
self.stream.skip() |
||||
|
return True |
||||
|
return False |
||||
|
|
||||
|
while 1: |
||||
|
if node.names: |
||||
|
self.stream.expect('comma') |
||||
|
if self.stream.current.type == 'name': |
||||
|
if parse_context(): |
||||
|
break |
||||
|
target = self.parse_assign_target(name_only=True) |
||||
|
if target.name.startswith('_'): |
||||
|
self.fail('names starting with an underline can not ' |
||||
|
'be imported', target.lineno, |
||||
|
exc=TemplateAssertionError) |
||||
|
if self.stream.skip_if('name:as'): |
||||
|
alias = self.parse_assign_target(name_only=True) |
||||
|
node.names.append((target.name, alias.name)) |
||||
|
else: |
||||
|
node.names.append(target.name) |
||||
|
if parse_context() or self.stream.current.type != 'comma': |
||||
|
break |
||||
|
else: |
||||
|
break |
||||
|
if not hasattr(node, 'with_context'): |
||||
|
node.with_context = False |
||||
|
self.stream.skip_if('comma') |
||||
|
return node |
||||
|
|
||||
|
def parse_signature(self, node): |
||||
|
node.args = args = [] |
||||
|
node.defaults = defaults = [] |
||||
|
self.stream.expect('lparen') |
||||
|
while self.stream.current.type != 'rparen': |
||||
|
if args: |
||||
|
self.stream.expect('comma') |
||||
|
arg = self.parse_assign_target(name_only=True) |
||||
|
arg.set_ctx('param') |
||||
|
if self.stream.skip_if('assign'): |
||||
|
defaults.append(self.parse_expression()) |
||||
|
elif defaults: |
||||
|
self.fail('non-default argument follows default argument') |
||||
|
args.append(arg) |
||||
|
self.stream.expect('rparen') |
||||
|
|
||||
|
def parse_call_block(self): |
||||
|
node = nodes.CallBlock(lineno=next(self.stream).lineno) |
||||
|
if self.stream.current.type == 'lparen': |
||||
|
self.parse_signature(node) |
||||
|
else: |
||||
|
node.args = [] |
||||
|
node.defaults = [] |
||||
|
|
||||
|
node.call = self.parse_expression() |
||||
|
if not isinstance(node.call, nodes.Call): |
||||
|
self.fail('expected call', node.lineno) |
||||
|
node.body = self.parse_statements(('name:endcall',), drop_needle=True) |
||||
|
return node |
||||
|
|
||||
|
def parse_filter_block(self): |
||||
|
node = nodes.FilterBlock(lineno=next(self.stream).lineno) |
||||
|
node.filter = self.parse_filter(None, start_inline=True) |
||||
|
node.body = self.parse_statements(('name:endfilter',), |
||||
|
drop_needle=True) |
||||
|
return node |
||||
|
|
||||
|
def parse_macro(self): |
||||
|
node = nodes.Macro(lineno=next(self.stream).lineno) |
||||
|
node.name = self.parse_assign_target(name_only=True).name |
||||
|
self.parse_signature(node) |
||||
|
node.body = self.parse_statements(('name:endmacro',), |
||||
|
drop_needle=True) |
||||
|
return node |
||||
|
|
||||
|
def parse_print(self): |
||||
|
node = nodes.Output(lineno=next(self.stream).lineno) |
||||
|
node.nodes = [] |
||||
|
while self.stream.current.type != 'block_end': |
||||
|
if node.nodes: |
||||
|
self.stream.expect('comma') |
||||
|
node.nodes.append(self.parse_expression()) |
||||
|
return node |
||||
|
|
||||
|
def parse_assign_target(self, with_tuple=True, name_only=False, |
||||
|
extra_end_rules=None): |
||||
|
"""Parse an assignment target. As Jinja2 allows assignments to |
||||
|
tuples, this function can parse all allowed assignment targets. Per |
||||
|
default assignments to tuples are parsed, that can be disable however |
||||
|
by setting `with_tuple` to `False`. If only assignments to names are |
||||
|
wanted `name_only` can be set to `True`. The `extra_end_rules` |
||||
|
parameter is forwarded to the tuple parsing function. |
||||
|
""" |
||||
|
if name_only: |
||||
|
token = self.stream.expect('name') |
||||
|
target = nodes.Name(token.value, 'store', lineno=token.lineno) |
||||
|
else: |
||||
|
if with_tuple: |
||||
|
target = self.parse_tuple(simplified=True, |
||||
|
extra_end_rules=extra_end_rules) |
||||
|
else: |
||||
|
target = self.parse_primary() |
||||
|
target.set_ctx('store') |
||||
|
if not target.can_assign(): |
||||
|
self.fail('can\'t assign to %r' % target.__class__. |
||||
|
__name__.lower(), target.lineno) |
||||
|
return target |
||||
|
|
||||
|
def parse_expression(self, with_condexpr=True): |
||||
|
"""Parse an expression. Per default all expressions are parsed, if |
||||
|
the optional `with_condexpr` parameter is set to `False` conditional |
||||
|
expressions are not parsed. |
||||
|
""" |
||||
|
if with_condexpr: |
||||
|
return self.parse_condexpr() |
||||
|
return self.parse_or() |
||||
|
|
||||
|
def parse_condexpr(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
expr1 = self.parse_or() |
||||
|
while self.stream.skip_if('name:if'): |
||||
|
expr2 = self.parse_or() |
||||
|
if self.stream.skip_if('name:else'): |
||||
|
expr3 = self.parse_condexpr() |
||||
|
else: |
||||
|
expr3 = None |
||||
|
expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return expr1 |
||||
|
|
||||
|
def parse_or(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
left = self.parse_and() |
||||
|
while self.stream.skip_if('name:or'): |
||||
|
right = self.parse_and() |
||||
|
left = nodes.Or(left, right, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return left |
||||
|
|
||||
|
def parse_and(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
left = self.parse_not() |
||||
|
while self.stream.skip_if('name:and'): |
||||
|
right = self.parse_not() |
||||
|
left = nodes.And(left, right, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return left |
||||
|
|
||||
|
def parse_not(self): |
||||
|
if self.stream.current.test('name:not'): |
||||
|
lineno = next(self.stream).lineno |
||||
|
return nodes.Not(self.parse_not(), lineno=lineno) |
||||
|
return self.parse_compare() |
||||
|
|
||||
|
def parse_compare(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
expr = self.parse_add() |
||||
|
ops = [] |
||||
|
while 1: |
||||
|
token_type = self.stream.current.type |
||||
|
if token_type in _compare_operators: |
||||
|
next(self.stream) |
||||
|
ops.append(nodes.Operand(token_type, self.parse_add())) |
||||
|
elif self.stream.skip_if('name:in'): |
||||
|
ops.append(nodes.Operand('in', self.parse_add())) |
||||
|
elif (self.stream.current.test('name:not') and |
||||
|
self.stream.look().test('name:in')): |
||||
|
self.stream.skip(2) |
||||
|
ops.append(nodes.Operand('notin', self.parse_add())) |
||||
|
else: |
||||
|
break |
||||
|
lineno = self.stream.current.lineno |
||||
|
if not ops: |
||||
|
return expr |
||||
|
return nodes.Compare(expr, ops, lineno=lineno) |
||||
|
|
||||
|
def parse_add(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
left = self.parse_sub() |
||||
|
while self.stream.current.type == 'add': |
||||
|
next(self.stream) |
||||
|
right = self.parse_sub() |
||||
|
left = nodes.Add(left, right, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return left |
||||
|
|
||||
|
def parse_sub(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
left = self.parse_concat() |
||||
|
while self.stream.current.type == 'sub': |
||||
|
next(self.stream) |
||||
|
right = self.parse_concat() |
||||
|
left = nodes.Sub(left, right, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return left |
||||
|
|
||||
|
def parse_concat(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
args = [self.parse_mul()] |
||||
|
while self.stream.current.type == 'tilde': |
||||
|
next(self.stream) |
||||
|
args.append(self.parse_mul()) |
||||
|
if len(args) == 1: |
||||
|
return args[0] |
||||
|
return nodes.Concat(args, lineno=lineno) |
||||
|
|
||||
|
def parse_mul(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
left = self.parse_div() |
||||
|
while self.stream.current.type == 'mul': |
||||
|
next(self.stream) |
||||
|
right = self.parse_div() |
||||
|
left = nodes.Mul(left, right, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return left |
||||
|
|
||||
|
def parse_div(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
left = self.parse_floordiv() |
||||
|
while self.stream.current.type == 'div': |
||||
|
next(self.stream) |
||||
|
right = self.parse_floordiv() |
||||
|
left = nodes.Div(left, right, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return left |
||||
|
|
||||
|
def parse_floordiv(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
left = self.parse_mod() |
||||
|
while self.stream.current.type == 'floordiv': |
||||
|
next(self.stream) |
||||
|
right = self.parse_mod() |
||||
|
left = nodes.FloorDiv(left, right, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return left |
||||
|
|
||||
|
def parse_mod(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
left = self.parse_pow() |
||||
|
while self.stream.current.type == 'mod': |
||||
|
next(self.stream) |
||||
|
right = self.parse_pow() |
||||
|
left = nodes.Mod(left, right, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return left |
||||
|
|
||||
|
def parse_pow(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
left = self.parse_unary() |
||||
|
while self.stream.current.type == 'pow': |
||||
|
next(self.stream) |
||||
|
right = self.parse_unary() |
||||
|
left = nodes.Pow(left, right, lineno=lineno) |
||||
|
lineno = self.stream.current.lineno |
||||
|
return left |
||||
|
|
||||
|
def parse_unary(self, with_filter=True): |
||||
|
token_type = self.stream.current.type |
||||
|
lineno = self.stream.current.lineno |
||||
|
if token_type == 'sub': |
||||
|
next(self.stream) |
||||
|
node = nodes.Neg(self.parse_unary(False), lineno=lineno) |
||||
|
elif token_type == 'add': |
||||
|
next(self.stream) |
||||
|
node = nodes.Pos(self.parse_unary(False), lineno=lineno) |
||||
|
else: |
||||
|
node = self.parse_primary() |
||||
|
node = self.parse_postfix(node) |
||||
|
if with_filter: |
||||
|
node = self.parse_filter_expr(node) |
||||
|
return node |
||||
|
|
||||
|
def parse_primary(self): |
||||
|
token = self.stream.current |
||||
|
if token.type == 'name': |
||||
|
if token.value in ('true', 'false', 'True', 'False'): |
||||
|
node = nodes.Const(token.value in ('true', 'True'), |
||||
|
lineno=token.lineno) |
||||
|
elif token.value in ('none', 'None'): |
||||
|
node = nodes.Const(None, lineno=token.lineno) |
||||
|
else: |
||||
|
node = nodes.Name(token.value, 'load', lineno=token.lineno) |
||||
|
next(self.stream) |
||||
|
elif token.type == 'string': |
||||
|
next(self.stream) |
||||
|
buf = [token.value] |
||||
|
lineno = token.lineno |
||||
|
while self.stream.current.type == 'string': |
||||
|
buf.append(self.stream.current.value) |
||||
|
next(self.stream) |
||||
|
node = nodes.Const(''.join(buf), lineno=lineno) |
||||
|
elif token.type in ('integer', 'float'): |
||||
|
next(self.stream) |
||||
|
node = nodes.Const(token.value, lineno=token.lineno) |
||||
|
elif token.type == 'lparen': |
||||
|
next(self.stream) |
||||
|
node = self.parse_tuple(explicit_parentheses=True) |
||||
|
self.stream.expect('rparen') |
||||
|
elif token.type == 'lbracket': |
||||
|
node = self.parse_list() |
||||
|
elif token.type == 'lbrace': |
||||
|
node = self.parse_dict() |
||||
|
else: |
||||
|
self.fail("unexpected '%s'" % describe_token(token), token.lineno) |
||||
|
return node |
||||
|
|
||||
|
def parse_tuple(self, simplified=False, with_condexpr=True, |
||||
|
extra_end_rules=None, explicit_parentheses=False): |
||||
|
"""Works like `parse_expression` but if multiple expressions are |
||||
|
delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created. |
||||
|
This method could also return a regular expression instead of a tuple |
||||
|
if no commas where found. |
||||
|
|
||||
|
The default parsing mode is a full tuple. If `simplified` is `True` |
||||
|
only names and literals are parsed. The `no_condexpr` parameter is |
||||
|
forwarded to :meth:`parse_expression`. |
||||
|
|
||||
|
Because tuples do not require delimiters and may end in a bogus comma |
||||
|
an extra hint is needed that marks the end of a tuple. For example |
||||
|
for loops support tuples between `for` and `in`. In that case the |
||||
|
`extra_end_rules` is set to ``['name:in']``. |
||||
|
|
||||
|
`explicit_parentheses` is true if the parsing was triggered by an |
||||
|
expression in parentheses. This is used to figure out if an empty |
||||
|
tuple is a valid expression or not. |
||||
|
""" |
||||
|
lineno = self.stream.current.lineno |
||||
|
if simplified: |
||||
|
parse = self.parse_primary |
||||
|
elif with_condexpr: |
||||
|
parse = self.parse_expression |
||||
|
else: |
||||
|
parse = lambda: self.parse_expression(with_condexpr=False) |
||||
|
args = [] |
||||
|
is_tuple = False |
||||
|
while 1: |
||||
|
if args: |
||||
|
self.stream.expect('comma') |
||||
|
if self.is_tuple_end(extra_end_rules): |
||||
|
break |
||||
|
args.append(parse()) |
||||
|
if self.stream.current.type == 'comma': |
||||
|
is_tuple = True |
||||
|
else: |
||||
|
break |
||||
|
lineno = self.stream.current.lineno |
||||
|
|
||||
|
if not is_tuple: |
||||
|
if args: |
||||
|
return args[0] |
||||
|
|
||||
|
# if we don't have explicit parentheses, an empty tuple is |
||||
|
# not a valid expression. This would mean nothing (literally |
||||
|
# nothing) in the spot of an expression would be an empty |
||||
|
# tuple. |
||||
|
if not explicit_parentheses: |
||||
|
self.fail('Expected an expression, got \'%s\'' % |
||||
|
describe_token(self.stream.current)) |
||||
|
|
||||
|
return nodes.Tuple(args, 'load', lineno=lineno) |
||||
|
|
||||
|
def parse_list(self): |
||||
|
token = self.stream.expect('lbracket') |
||||
|
items = [] |
||||
|
while self.stream.current.type != 'rbracket': |
||||
|
if items: |
||||
|
self.stream.expect('comma') |
||||
|
if self.stream.current.type == 'rbracket': |
||||
|
break |
||||
|
items.append(self.parse_expression()) |
||||
|
self.stream.expect('rbracket') |
||||
|
return nodes.List(items, lineno=token.lineno) |
||||
|
|
||||
|
def parse_dict(self): |
||||
|
token = self.stream.expect('lbrace') |
||||
|
items = [] |
||||
|
while self.stream.current.type != 'rbrace': |
||||
|
if items: |
||||
|
self.stream.expect('comma') |
||||
|
if self.stream.current.type == 'rbrace': |
||||
|
break |
||||
|
key = self.parse_expression() |
||||
|
self.stream.expect('colon') |
||||
|
value = self.parse_expression() |
||||
|
items.append(nodes.Pair(key, value, lineno=key.lineno)) |
||||
|
self.stream.expect('rbrace') |
||||
|
return nodes.Dict(items, lineno=token.lineno) |
||||
|
|
||||
|
def parse_postfix(self, node): |
||||
|
while 1: |
||||
|
token_type = self.stream.current.type |
||||
|
if token_type == 'dot' or token_type == 'lbracket': |
||||
|
node = self.parse_subscript(node) |
||||
|
# calls are valid both after postfix expressions (getattr |
||||
|
# and getitem) as well as filters and tests |
||||
|
elif token_type == 'lparen': |
||||
|
node = self.parse_call(node) |
||||
|
else: |
||||
|
break |
||||
|
return node |
||||
|
|
||||
|
def parse_filter_expr(self, node): |
||||
|
while 1: |
||||
|
token_type = self.stream.current.type |
||||
|
if token_type == 'pipe': |
||||
|
node = self.parse_filter(node) |
||||
|
elif token_type == 'name' and self.stream.current.value == 'is': |
||||
|
node = self.parse_test(node) |
||||
|
# calls are valid both after postfix expressions (getattr |
||||
|
# and getitem) as well as filters and tests |
||||
|
elif token_type == 'lparen': |
||||
|
node = self.parse_call(node) |
||||
|
else: |
||||
|
break |
||||
|
return node |
||||
|
|
||||
|
def parse_subscript(self, node): |
||||
|
token = next(self.stream) |
||||
|
if token.type == 'dot': |
||||
|
attr_token = self.stream.current |
||||
|
next(self.stream) |
||||
|
if attr_token.type == 'name': |
||||
|
return nodes.Getattr(node, attr_token.value, 'load', |
||||
|
lineno=token.lineno) |
||||
|
elif attr_token.type != 'integer': |
||||
|
self.fail('expected name or number', attr_token.lineno) |
||||
|
arg = nodes.Const(attr_token.value, lineno=attr_token.lineno) |
||||
|
return nodes.Getitem(node, arg, 'load', lineno=token.lineno) |
||||
|
if token.type == 'lbracket': |
||||
|
args = [] |
||||
|
while self.stream.current.type != 'rbracket': |
||||
|
if args: |
||||
|
self.stream.expect('comma') |
||||
|
args.append(self.parse_subscribed()) |
||||
|
self.stream.expect('rbracket') |
||||
|
if len(args) == 1: |
||||
|
arg = args[0] |
||||
|
else: |
||||
|
arg = nodes.Tuple(args, 'load', lineno=token.lineno) |
||||
|
return nodes.Getitem(node, arg, 'load', lineno=token.lineno) |
||||
|
self.fail('expected subscript expression', self.lineno) |
||||
|
|
||||
|
def parse_subscribed(self): |
||||
|
lineno = self.stream.current.lineno |
||||
|
|
||||
|
if self.stream.current.type == 'colon': |
||||
|
next(self.stream) |
||||
|
args = [None] |
||||
|
else: |
||||
|
node = self.parse_expression() |
||||
|
if self.stream.current.type != 'colon': |
||||
|
return node |
||||
|
next(self.stream) |
||||
|
args = [node] |
||||
|
|
||||
|
if self.stream.current.type == 'colon': |
||||
|
args.append(None) |
||||
|
elif self.stream.current.type not in ('rbracket', 'comma'): |
||||
|
args.append(self.parse_expression()) |
||||
|
else: |
||||
|
args.append(None) |
||||
|
|
||||
|
if self.stream.current.type == 'colon': |
||||
|
next(self.stream) |
||||
|
if self.stream.current.type not in ('rbracket', 'comma'): |
||||
|
args.append(self.parse_expression()) |
||||
|
else: |
||||
|
args.append(None) |
||||
|
else: |
||||
|
args.append(None) |
||||
|
|
||||
|
return nodes.Slice(lineno=lineno, *args) |
||||
|
|
||||
|
def parse_call(self, node): |
||||
|
token = self.stream.expect('lparen') |
||||
|
args = [] |
||||
|
kwargs = [] |
||||
|
dyn_args = dyn_kwargs = None |
||||
|
require_comma = False |
||||
|
|
||||
|
def ensure(expr): |
||||
|
if not expr: |
||||
|
self.fail('invalid syntax for function call expression', |
||||
|
token.lineno) |
||||
|
|
||||
|
while self.stream.current.type != 'rparen': |
||||
|
if require_comma: |
||||
|
self.stream.expect('comma') |
||||
|
# support for trailing comma |
||||
|
if self.stream.current.type == 'rparen': |
||||
|
break |
||||
|
if self.stream.current.type == 'mul': |
||||
|
ensure(dyn_args is None and dyn_kwargs is None) |
||||
|
next(self.stream) |
||||
|
dyn_args = self.parse_expression() |
||||
|
elif self.stream.current.type == 'pow': |
||||
|
ensure(dyn_kwargs is None) |
||||
|
next(self.stream) |
||||
|
dyn_kwargs = self.parse_expression() |
||||
|
else: |
||||
|
ensure(dyn_args is None and dyn_kwargs is None) |
||||
|
if self.stream.current.type == 'name' and \ |
||||
|
self.stream.look().type == 'assign': |
||||
|
key = self.stream.current.value |
||||
|
self.stream.skip(2) |
||||
|
value = self.parse_expression() |
||||
|
kwargs.append(nodes.Keyword(key, value, |
||||
|
lineno=value.lineno)) |
||||
|
else: |
||||
|
ensure(not kwargs) |
||||
|
args.append(self.parse_expression()) |
||||
|
|
||||
|
require_comma = True |
||||
|
self.stream.expect('rparen') |
||||
|
|
||||
|
if node is None: |
||||
|
return args, kwargs, dyn_args, dyn_kwargs |
||||
|
return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, |
||||
|
lineno=token.lineno) |
||||
|
|
||||
|
def parse_filter(self, node, start_inline=False): |
||||
|
while self.stream.current.type == 'pipe' or start_inline: |
||||
|
if not start_inline: |
||||
|
next(self.stream) |
||||
|
token = self.stream.expect('name') |
||||
|
name = token.value |
||||
|
while self.stream.current.type == 'dot': |
||||
|
next(self.stream) |
||||
|
name += '.' + self.stream.expect('name').value |
||||
|
if self.stream.current.type == 'lparen': |
||||
|
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) |
||||
|
else: |
||||
|
args = [] |
||||
|
kwargs = [] |
||||
|
dyn_args = dyn_kwargs = None |
||||
|
node = nodes.Filter(node, name, args, kwargs, dyn_args, |
||||
|
dyn_kwargs, lineno=token.lineno) |
||||
|
start_inline = False |
||||
|
return node |
||||
|
|
||||
|
def parse_test(self, node): |
||||
|
token = next(self.stream) |
||||
|
if self.stream.current.test('name:not'): |
||||
|
next(self.stream) |
||||
|
negated = True |
||||
|
else: |
||||
|
negated = False |
||||
|
name = self.stream.expect('name').value |
||||
|
while self.stream.current.type == 'dot': |
||||
|
next(self.stream) |
||||
|
name += '.' + self.stream.expect('name').value |
||||
|
dyn_args = dyn_kwargs = None |
||||
|
kwargs = [] |
||||
|
if self.stream.current.type == 'lparen': |
||||
|
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) |
||||
|
elif (self.stream.current.type in ('name', 'string', 'integer', |
||||
|
'float', 'lparen', 'lbracket', |
||||
|
'lbrace') and not |
||||
|
self.stream.current.test_any('name:else', 'name:or', |
||||
|
'name:and')): |
||||
|
if self.stream.current.test('name:is'): |
||||
|
self.fail('You cannot chain multiple tests with is') |
||||
|
args = [self.parse_expression()] |
||||
|
else: |
||||
|
args = [] |
||||
|
node = nodes.Test(node, name, args, kwargs, dyn_args, |
||||
|
dyn_kwargs, lineno=token.lineno) |
||||
|
if negated: |
||||
|
node = nodes.Not(node, lineno=token.lineno) |
||||
|
return node |
||||
|
|
||||
|
def subparse(self, end_tokens=None): |
||||
|
body = [] |
||||
|
data_buffer = [] |
||||
|
add_data = data_buffer.append |
||||
|
|
||||
|
if end_tokens is not None: |
||||
|
self._end_token_stack.append(end_tokens) |
||||
|
|
||||
|
def flush_data(): |
||||
|
if data_buffer: |
||||
|
lineno = data_buffer[0].lineno |
||||
|
body.append(nodes.Output(data_buffer[:], lineno=lineno)) |
||||
|
del data_buffer[:] |
||||
|
|
||||
|
try: |
||||
|
while self.stream: |
||||
|
token = self.stream.current |
||||
|
if token.type == 'data': |
||||
|
if token.value: |
||||
|
add_data(nodes.TemplateData(token.value, |
||||
|
lineno=token.lineno)) |
||||
|
next(self.stream) |
||||
|
elif token.type == 'variable_begin': |
||||
|
next(self.stream) |
||||
|
add_data(self.parse_tuple(with_condexpr=True)) |
||||
|
self.stream.expect('variable_end') |
||||
|
elif token.type == 'block_begin': |
||||
|
flush_data() |
||||
|
next(self.stream) |
||||
|
if end_tokens is not None and \ |
||||
|
self.stream.current.test_any(*end_tokens): |
||||
|
return body |
||||
|
rv = self.parse_statement() |
||||
|
if isinstance(rv, list): |
||||
|
body.extend(rv) |
||||
|
else: |
||||
|
body.append(rv) |
||||
|
self.stream.expect('block_end') |
||||
|
else: |
||||
|
raise AssertionError('internal parsing error') |
||||
|
|
||||
|
flush_data() |
||||
|
finally: |
||||
|
if end_tokens is not None: |
||||
|
self._end_token_stack.pop() |
||||
|
|
||||
|
return body |
||||
|
|
||||
|
def parse(self): |
||||
|
"""Parse the whole template into a `Template` node.""" |
||||
|
result = nodes.Template(self.subparse(), lineno=1) |
||||
|
result.set_environment(self.environment) |
||||
|
return result |
@ -0,0 +1,667 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.runtime |
||||
|
~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Runtime helpers. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
import sys |
||||
|
|
||||
|
from itertools import chain |
||||
|
from jinja2.nodes import EvalContext, _context_function_types |
||||
|
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \ |
||||
|
internalcode, object_type_repr |
||||
|
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \ |
||||
|
TemplateNotFound |
||||
|
from jinja2._compat import imap, text_type, iteritems, \ |
||||
|
implements_iterator, implements_to_string, string_types, PY2 |
||||
|
|
||||
|
|
||||
|
# these variables are exported to the template runtime |
||||
|
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup', |
||||
|
'TemplateRuntimeError', 'missing', 'concat', 'escape', |
||||
|
'markup_join', 'unicode_join', 'to_string', 'identity', |
||||
|
'TemplateNotFound', 'make_logging_undefined'] |
||||
|
|
||||
|
#: the name of the function that is used to convert something into |
||||
|
#: a string. We can just use the text type here. |
||||
|
to_string = text_type |
||||
|
|
||||
|
#: the identity function. Useful for certain things in the environment |
||||
|
identity = lambda x: x |
||||
|
|
||||
|
_last_iteration = object() |
||||
|
|
||||
|
|
||||
|
def markup_join(seq): |
||||
|
"""Concatenation that escapes if necessary and converts to unicode.""" |
||||
|
buf = [] |
||||
|
iterator = imap(soft_unicode, seq) |
||||
|
for arg in iterator: |
||||
|
buf.append(arg) |
||||
|
if hasattr(arg, '__html__'): |
||||
|
return Markup(u'').join(chain(buf, iterator)) |
||||
|
return concat(buf) |
||||
|
|
||||
|
|
||||
|
def unicode_join(seq): |
||||
|
"""Simple args to unicode conversion and concatenation.""" |
||||
|
return concat(imap(text_type, seq)) |
||||
|
|
||||
|
|
||||
|
def new_context(environment, template_name, blocks, vars=None, |
||||
|
shared=None, globals=None, locals=None): |
||||
|
"""Internal helper to for context creation.""" |
||||
|
if vars is None: |
||||
|
vars = {} |
||||
|
if shared: |
||||
|
parent = vars |
||||
|
else: |
||||
|
parent = dict(globals or (), **vars) |
||||
|
if locals: |
||||
|
# if the parent is shared a copy should be created because |
||||
|
# we don't want to modify the dict passed |
||||
|
if shared: |
||||
|
parent = dict(parent) |
||||
|
for key, value in iteritems(locals): |
||||
|
if key[:2] == 'l_' and value is not missing: |
||||
|
parent[key[2:]] = value |
||||
|
return environment.context_class(environment, parent, template_name, |
||||
|
blocks) |
||||
|
|
||||
|
|
||||
|
class TemplateReference(object): |
||||
|
"""The `self` in templates.""" |
||||
|
|
||||
|
def __init__(self, context): |
||||
|
self.__context = context |
||||
|
|
||||
|
def __getitem__(self, name): |
||||
|
blocks = self.__context.blocks[name] |
||||
|
return BlockReference(name, self.__context, blocks, 0) |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<%s %r>' % ( |
||||
|
self.__class__.__name__, |
||||
|
self.__context.name |
||||
|
) |
||||
|
|
||||
|
|
||||
|
class Context(object): |
||||
|
"""The template context holds the variables of a template. It stores the |
||||
|
values passed to the template and also the names the template exports. |
||||
|
Creating instances is neither supported nor useful as it's created |
||||
|
automatically at various stages of the template evaluation and should not |
||||
|
be created by hand. |
||||
|
|
||||
|
The context is immutable. Modifications on :attr:`parent` **must not** |
||||
|
happen and modifications on :attr:`vars` are allowed from generated |
||||
|
template code only. Template filters and global functions marked as |
||||
|
:func:`contextfunction`\s get the active context passed as first argument |
||||
|
and are allowed to access the context read-only. |
||||
|
|
||||
|
The template context supports read only dict operations (`get`, |
||||
|
`keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`, |
||||
|
`__getitem__`, `__contains__`). Additionally there is a :meth:`resolve` |
||||
|
method that doesn't fail with a `KeyError` but returns an |
||||
|
:class:`Undefined` object for missing variables. |
||||
|
""" |
||||
|
__slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars', |
||||
|
'name', 'blocks', '__weakref__') |
||||
|
|
||||
|
def __init__(self, environment, parent, name, blocks): |
||||
|
self.parent = parent |
||||
|
self.vars = {} |
||||
|
self.environment = environment |
||||
|
self.eval_ctx = EvalContext(self.environment, name) |
||||
|
self.exported_vars = set() |
||||
|
self.name = name |
||||
|
|
||||
|
# create the initial mapping of blocks. Whenever template inheritance |
||||
|
# takes place the runtime will update this mapping with the new blocks |
||||
|
# from the template. |
||||
|
self.blocks = dict((k, [v]) for k, v in iteritems(blocks)) |
||||
|
|
||||
|
def super(self, name, current): |
||||
|
"""Render a parent block.""" |
||||
|
try: |
||||
|
blocks = self.blocks[name] |
||||
|
index = blocks.index(current) + 1 |
||||
|
blocks[index] |
||||
|
except LookupError: |
||||
|
return self.environment.undefined('there is no parent block ' |
||||
|
'called %r.' % name, |
||||
|
name='super') |
||||
|
return BlockReference(name, self, blocks, index) |
||||
|
|
||||
|
def get(self, key, default=None): |
||||
|
"""Returns an item from the template context, if it doesn't exist |
||||
|
`default` is returned. |
||||
|
""" |
||||
|
try: |
||||
|
return self[key] |
||||
|
except KeyError: |
||||
|
return default |
||||
|
|
||||
|
def resolve(self, key): |
||||
|
"""Looks up a variable like `__getitem__` or `get` but returns an |
||||
|
:class:`Undefined` object with the name of the name looked up. |
||||
|
""" |
||||
|
if key in self.vars: |
||||
|
return self.vars[key] |
||||
|
if key in self.parent: |
||||
|
return self.parent[key] |
||||
|
return self.environment.undefined(name=key) |
||||
|
|
||||
|
def get_exported(self): |
||||
|
"""Get a new dict with the exported variables.""" |
||||
|
return dict((k, self.vars[k]) for k in self.exported_vars) |
||||
|
|
||||
|
def get_all(self): |
||||
|
"""Return a copy of the complete context as dict including the |
||||
|
exported variables. |
||||
|
""" |
||||
|
return dict(self.parent, **self.vars) |
||||
|
|
||||
|
@internalcode |
||||
|
def call(__self, __obj, *args, **kwargs): |
||||
|
"""Call the callable with the arguments and keyword arguments |
||||
|
provided but inject the active context or environment as first |
||||
|
argument if the callable is a :func:`contextfunction` or |
||||
|
:func:`environmentfunction`. |
||||
|
""" |
||||
|
if __debug__: |
||||
|
__traceback_hide__ = True # noqa |
||||
|
|
||||
|
# Allow callable classes to take a context |
||||
|
fn = __obj.__call__ |
||||
|
for fn_type in ('contextfunction', |
||||
|
'evalcontextfunction', |
||||
|
'environmentfunction'): |
||||
|
if hasattr(fn, fn_type): |
||||
|
__obj = fn |
||||
|
break |
||||
|
|
||||
|
if isinstance(__obj, _context_function_types): |
||||
|
if getattr(__obj, 'contextfunction', 0): |
||||
|
args = (__self,) + args |
||||
|
elif getattr(__obj, 'evalcontextfunction', 0): |
||||
|
args = (__self.eval_ctx,) + args |
||||
|
elif getattr(__obj, 'environmentfunction', 0): |
||||
|
args = (__self.environment,) + args |
||||
|
try: |
||||
|
return __obj(*args, **kwargs) |
||||
|
except StopIteration: |
||||
|
return __self.environment.undefined('value was undefined because ' |
||||
|
'a callable raised a ' |
||||
|
'StopIteration exception') |
||||
|
|
||||
|
def derived(self, locals=None): |
||||
|
"""Internal helper function to create a derived context.""" |
||||
|
context = new_context(self.environment, self.name, {}, |
||||
|
self.parent, True, None, locals) |
||||
|
context.vars.update(self.vars) |
||||
|
context.eval_ctx = self.eval_ctx |
||||
|
context.blocks.update((k, list(v)) for k, v in iteritems(self.blocks)) |
||||
|
return context |
||||
|
|
||||
|
def _all(meth): |
||||
|
proxy = lambda self: getattr(self.get_all(), meth)() |
||||
|
proxy.__doc__ = getattr(dict, meth).__doc__ |
||||
|
proxy.__name__ = meth |
||||
|
return proxy |
||||
|
|
||||
|
keys = _all('keys') |
||||
|
values = _all('values') |
||||
|
items = _all('items') |
||||
|
|
||||
|
# not available on python 3 |
||||
|
if PY2: |
||||
|
iterkeys = _all('iterkeys') |
||||
|
itervalues = _all('itervalues') |
||||
|
iteritems = _all('iteritems') |
||||
|
del _all |
||||
|
|
||||
|
def __contains__(self, name): |
||||
|
return name in self.vars or name in self.parent |
||||
|
|
||||
|
def __getitem__(self, key): |
||||
|
"""Lookup a variable or raise `KeyError` if the variable is |
||||
|
undefined. |
||||
|
""" |
||||
|
item = self.resolve(key) |
||||
|
if isinstance(item, Undefined): |
||||
|
raise KeyError(key) |
||||
|
return item |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<%s %s of %r>' % ( |
||||
|
self.__class__.__name__, |
||||
|
repr(self.get_all()), |
||||
|
self.name |
||||
|
) |
||||
|
|
||||
|
|
||||
|
# register the context as mapping if possible |
||||
|
try: |
||||
|
from collections import Mapping |
||||
|
Mapping.register(Context) |
||||
|
except ImportError: |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
class BlockReference(object): |
||||
|
"""One block on a template reference.""" |
||||
|
|
||||
|
def __init__(self, name, context, stack, depth): |
||||
|
self.name = name |
||||
|
self._context = context |
||||
|
self._stack = stack |
||||
|
self._depth = depth |
||||
|
|
||||
|
@property |
||||
|
def super(self): |
||||
|
"""Super the block.""" |
||||
|
if self._depth + 1 >= len(self._stack): |
||||
|
return self._context.environment. \ |
||||
|
undefined('there is no parent block called %r.' % |
||||
|
self.name, name='super') |
||||
|
return BlockReference(self.name, self._context, self._stack, |
||||
|
self._depth + 1) |
||||
|
|
||||
|
@internalcode |
||||
|
def __call__(self): |
||||
|
rv = concat(self._stack[self._depth](self._context)) |
||||
|
if self._context.eval_ctx.autoescape: |
||||
|
rv = Markup(rv) |
||||
|
return rv |
||||
|
|
||||
|
|
||||
|
class LoopContext(object): |
||||
|
"""A loop context for dynamic iteration.""" |
||||
|
|
||||
|
def __init__(self, iterable, recurse=None, depth0=0): |
||||
|
self._iterator = iter(iterable) |
||||
|
self._recurse = recurse |
||||
|
self._after = self._safe_next() |
||||
|
self.index0 = -1 |
||||
|
self.depth0 = depth0 |
||||
|
|
||||
|
# try to get the length of the iterable early. This must be done |
||||
|
# here because there are some broken iterators around where there |
||||
|
# __len__ is the number of iterations left (i'm looking at your |
||||
|
# listreverseiterator!). |
||||
|
try: |
||||
|
self._length = len(iterable) |
||||
|
except (TypeError, AttributeError): |
||||
|
self._length = None |
||||
|
|
||||
|
def cycle(self, *args): |
||||
|
"""Cycles among the arguments with the current loop index.""" |
||||
|
if not args: |
||||
|
raise TypeError('no items for cycling given') |
||||
|
return args[self.index0 % len(args)] |
||||
|
|
||||
|
first = property(lambda x: x.index0 == 0) |
||||
|
last = property(lambda x: x._after is _last_iteration) |
||||
|
index = property(lambda x: x.index0 + 1) |
||||
|
revindex = property(lambda x: x.length - x.index0) |
||||
|
revindex0 = property(lambda x: x.length - x.index) |
||||
|
depth = property(lambda x: x.depth0 + 1) |
||||
|
|
||||
|
def __len__(self): |
||||
|
return self.length |
||||
|
|
||||
|
def __iter__(self): |
||||
|
return LoopContextIterator(self) |
||||
|
|
||||
|
def _safe_next(self): |
||||
|
try: |
||||
|
return next(self._iterator) |
||||
|
except StopIteration: |
||||
|
return _last_iteration |
||||
|
|
||||
|
@internalcode |
||||
|
def loop(self, iterable): |
||||
|
if self._recurse is None: |
||||
|
raise TypeError('Tried to call non recursive loop. Maybe you ' |
||||
|
"forgot the 'recursive' modifier.") |
||||
|
return self._recurse(iterable, self._recurse, self.depth0 + 1) |
||||
|
|
||||
|
# a nifty trick to enhance the error message if someone tried to call |
||||
|
# the the loop without or with too many arguments. |
||||
|
__call__ = loop |
||||
|
del loop |
||||
|
|
||||
|
@property |
||||
|
def length(self): |
||||
|
if self._length is None: |
||||
|
# if was not possible to get the length of the iterator when |
||||
|
# the loop context was created (ie: iterating over a generator) |
||||
|
# we have to convert the iterable into a sequence and use the |
||||
|
# length of that + the number of iterations so far. |
||||
|
iterable = tuple(self._iterator) |
||||
|
self._iterator = iter(iterable) |
||||
|
iterations_done = self.index0 + 2 |
||||
|
self._length = len(iterable) + iterations_done |
||||
|
return self._length |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<%s %r/%r>' % ( |
||||
|
self.__class__.__name__, |
||||
|
self.index, |
||||
|
self.length |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@implements_iterator |
||||
|
class LoopContextIterator(object): |
||||
|
"""The iterator for a loop context.""" |
||||
|
__slots__ = ('context',) |
||||
|
|
||||
|
def __init__(self, context): |
||||
|
self.context = context |
||||
|
|
||||
|
def __iter__(self): |
||||
|
return self |
||||
|
|
||||
|
def __next__(self): |
||||
|
ctx = self.context |
||||
|
ctx.index0 += 1 |
||||
|
if ctx._after is _last_iteration: |
||||
|
raise StopIteration() |
||||
|
next_elem = ctx._after |
||||
|
ctx._after = ctx._safe_next() |
||||
|
return next_elem, ctx |
||||
|
|
||||
|
|
||||
|
class Macro(object): |
||||
|
"""Wraps a macro function.""" |
||||
|
|
||||
|
def __init__(self, environment, func, name, arguments, defaults, |
||||
|
catch_kwargs, catch_varargs, caller): |
||||
|
self._environment = environment |
||||
|
self._func = func |
||||
|
self._argument_count = len(arguments) |
||||
|
self.name = name |
||||
|
self.arguments = arguments |
||||
|
self.defaults = defaults |
||||
|
self.catch_kwargs = catch_kwargs |
||||
|
self.catch_varargs = catch_varargs |
||||
|
self.caller = caller |
||||
|
|
||||
|
@internalcode |
||||
|
def __call__(self, *args, **kwargs): |
||||
|
# try to consume the positional arguments |
||||
|
arguments = list(args[:self._argument_count]) |
||||
|
off = len(arguments) |
||||
|
|
||||
|
# if the number of arguments consumed is not the number of |
||||
|
# arguments expected we start filling in keyword arguments |
||||
|
# and defaults. |
||||
|
if off != self._argument_count: |
||||
|
for idx, name in enumerate(self.arguments[len(arguments):]): |
||||
|
try: |
||||
|
value = kwargs.pop(name) |
||||
|
except KeyError: |
||||
|
try: |
||||
|
value = self.defaults[idx - self._argument_count + off] |
||||
|
except IndexError: |
||||
|
value = self._environment.undefined( |
||||
|
'parameter %r was not provided' % name, name=name) |
||||
|
arguments.append(value) |
||||
|
|
||||
|
# it's important that the order of these arguments does not change |
||||
|
# if not also changed in the compiler's `function_scoping` method. |
||||
|
# the order is caller, keyword arguments, positional arguments! |
||||
|
if self.caller: |
||||
|
caller = kwargs.pop('caller', None) |
||||
|
if caller is None: |
||||
|
caller = self._environment.undefined('No caller defined', |
||||
|
name='caller') |
||||
|
arguments.append(caller) |
||||
|
if self.catch_kwargs: |
||||
|
arguments.append(kwargs) |
||||
|
elif kwargs: |
||||
|
raise TypeError('macro %r takes no keyword argument %r' % |
||||
|
(self.name, next(iter(kwargs)))) |
||||
|
if self.catch_varargs: |
||||
|
arguments.append(args[self._argument_count:]) |
||||
|
elif len(args) > self._argument_count: |
||||
|
raise TypeError('macro %r takes not more than %d argument(s)' % |
||||
|
(self.name, len(self.arguments))) |
||||
|
return self._func(*arguments) |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<%s %s>' % ( |
||||
|
self.__class__.__name__, |
||||
|
self.name is None and 'anonymous' or repr(self.name) |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@implements_to_string |
||||
|
class Undefined(object): |
||||
|
"""The default undefined type. This undefined type can be printed and |
||||
|
iterated over, but every other access will raise an :exc:`jinja2.exceptions.UndefinedError`: |
||||
|
|
||||
|
>>> foo = Undefined(name='foo') |
||||
|
>>> str(foo) |
||||
|
'' |
||||
|
>>> not foo |
||||
|
True |
||||
|
>>> foo + 42 |
||||
|
Traceback (most recent call last): |
||||
|
... |
||||
|
jinja2.exceptions.UndefinedError: 'foo' is undefined |
||||
|
""" |
||||
|
__slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name', |
||||
|
'_undefined_exception') |
||||
|
|
||||
|
def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError): |
||||
|
self._undefined_hint = hint |
||||
|
self._undefined_obj = obj |
||||
|
self._undefined_name = name |
||||
|
self._undefined_exception = exc |
||||
|
|
||||
|
@internalcode |
||||
|
def _fail_with_undefined_error(self, *args, **kwargs): |
||||
|
"""Regular callback function for undefined objects that raises an |
||||
|
`jinja2.exceptions.UndefinedError` on call. |
||||
|
""" |
||||
|
if self._undefined_hint is None: |
||||
|
if self._undefined_obj is missing: |
||||
|
hint = '%r is undefined' % self._undefined_name |
||||
|
elif not isinstance(self._undefined_name, string_types): |
||||
|
hint = '%s has no element %r' % ( |
||||
|
object_type_repr(self._undefined_obj), |
||||
|
self._undefined_name |
||||
|
) |
||||
|
else: |
||||
|
hint = '%r has no attribute %r' % ( |
||||
|
object_type_repr(self._undefined_obj), |
||||
|
self._undefined_name |
||||
|
) |
||||
|
else: |
||||
|
hint = self._undefined_hint |
||||
|
raise self._undefined_exception(hint) |
||||
|
|
||||
|
@internalcode |
||||
|
def __getattr__(self, name): |
||||
|
if name[:2] == '__': |
||||
|
raise AttributeError(name) |
||||
|
return self._fail_with_undefined_error() |
||||
|
|
||||
|
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \ |
||||
|
__truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \ |
||||
|
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \ |
||||
|
__getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \ |
||||
|
__float__ = __complex__ = __pow__ = __rpow__ = \ |
||||
|
_fail_with_undefined_error |
||||
|
|
||||
|
def __eq__(self, other): |
||||
|
return type(self) is type(other) |
||||
|
|
||||
|
def __ne__(self, other): |
||||
|
return not self.__eq__(other) |
||||
|
|
||||
|
def __hash__(self): |
||||
|
return id(type(self)) |
||||
|
|
||||
|
def __str__(self): |
||||
|
return u'' |
||||
|
|
||||
|
def __len__(self): |
||||
|
return 0 |
||||
|
|
||||
|
def __iter__(self): |
||||
|
if 0: |
||||
|
yield None |
||||
|
|
||||
|
def __nonzero__(self): |
||||
|
return False |
||||
|
__bool__ = __nonzero__ |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return 'Undefined' |
||||
|
|
||||
|
|
||||
|
def make_logging_undefined(logger=None, base=None): |
||||
|
"""Given a logger object this returns a new undefined class that will |
||||
|
log certain failures. It will log iterations and printing. If no |
||||
|
logger is given a default logger is created. |
||||
|
|
||||
|
Example:: |
||||
|
|
||||
|
logger = logging.getLogger(__name__) |
||||
|
LoggingUndefined = make_logging_undefined( |
||||
|
logger=logger, |
||||
|
base=Undefined |
||||
|
) |
||||
|
|
||||
|
.. versionadded:: 2.8 |
||||
|
|
||||
|
:param logger: the logger to use. If not provided, a default logger |
||||
|
is created. |
||||
|
:param base: the base class to add logging functionality to. This |
||||
|
defaults to :class:`Undefined`. |
||||
|
""" |
||||
|
if logger is None: |
||||
|
import logging |
||||
|
logger = logging.getLogger(__name__) |
||||
|
logger.addHandler(logging.StreamHandler(sys.stderr)) |
||||
|
if base is None: |
||||
|
base = Undefined |
||||
|
|
||||
|
def _log_message(undef): |
||||
|
if undef._undefined_hint is None: |
||||
|
if undef._undefined_obj is missing: |
||||
|
hint = '%s is undefined' % undef._undefined_name |
||||
|
elif not isinstance(undef._undefined_name, string_types): |
||||
|
hint = '%s has no element %s' % ( |
||||
|
object_type_repr(undef._undefined_obj), |
||||
|
undef._undefined_name) |
||||
|
else: |
||||
|
hint = '%s has no attribute %s' % ( |
||||
|
object_type_repr(undef._undefined_obj), |
||||
|
undef._undefined_name) |
||||
|
else: |
||||
|
hint = undef._undefined_hint |
||||
|
logger.warning('Template variable warning: %s', hint) |
||||
|
|
||||
|
class LoggingUndefined(base): |
||||
|
|
||||
|
def _fail_with_undefined_error(self, *args, **kwargs): |
||||
|
try: |
||||
|
return base._fail_with_undefined_error(self, *args, **kwargs) |
||||
|
except self._undefined_exception as e: |
||||
|
logger.error('Template variable error: %s', str(e)) |
||||
|
raise e |
||||
|
|
||||
|
def __str__(self): |
||||
|
rv = base.__str__(self) |
||||
|
_log_message(self) |
||||
|
return rv |
||||
|
|
||||
|
def __iter__(self): |
||||
|
rv = base.__iter__(self) |
||||
|
_log_message(self) |
||||
|
return rv |
||||
|
|
||||
|
if PY2: |
||||
|
def __nonzero__(self): |
||||
|
rv = base.__nonzero__(self) |
||||
|
_log_message(self) |
||||
|
return rv |
||||
|
|
||||
|
def __unicode__(self): |
||||
|
rv = base.__unicode__(self) |
||||
|
_log_message(self) |
||||
|
return rv |
||||
|
else: |
||||
|
def __bool__(self): |
||||
|
rv = base.__bool__(self) |
||||
|
_log_message(self) |
||||
|
return rv |
||||
|
|
||||
|
return LoggingUndefined |
||||
|
|
||||
|
|
||||
|
@implements_to_string |
||||
|
class DebugUndefined(Undefined): |
||||
|
"""An undefined that returns the debug info when printed. |
||||
|
|
||||
|
>>> foo = DebugUndefined(name='foo') |
||||
|
>>> str(foo) |
||||
|
'{{ foo }}' |
||||
|
>>> not foo |
||||
|
True |
||||
|
>>> foo + 42 |
||||
|
Traceback (most recent call last): |
||||
|
... |
||||
|
jinja2.exceptions.UndefinedError: 'foo' is undefined |
||||
|
""" |
||||
|
__slots__ = () |
||||
|
|
||||
|
def __str__(self): |
||||
|
if self._undefined_hint is None: |
||||
|
if self._undefined_obj is missing: |
||||
|
return u'{{ %s }}' % self._undefined_name |
||||
|
return '{{ no such element: %s[%r] }}' % ( |
||||
|
object_type_repr(self._undefined_obj), |
||||
|
self._undefined_name |
||||
|
) |
||||
|
return u'{{ undefined value printed: %s }}' % self._undefined_hint |
||||
|
|
||||
|
|
||||
|
@implements_to_string |
||||
|
class StrictUndefined(Undefined): |
||||
|
"""An undefined that barks on print and iteration as well as boolean |
||||
|
tests and all kinds of comparisons. In other words: you can do nothing |
||||
|
with it except checking if it's defined using the `defined` test. |
||||
|
|
||||
|
>>> foo = StrictUndefined(name='foo') |
||||
|
>>> str(foo) |
||||
|
Traceback (most recent call last): |
||||
|
... |
||||
|
jinja2.exceptions.UndefinedError: 'foo' is undefined |
||||
|
>>> not foo |
||||
|
Traceback (most recent call last): |
||||
|
... |
||||
|
jinja2.exceptions.UndefinedError: 'foo' is undefined |
||||
|
>>> foo + 42 |
||||
|
Traceback (most recent call last): |
||||
|
... |
||||
|
jinja2.exceptions.UndefinedError: 'foo' is undefined |
||||
|
""" |
||||
|
__slots__ = () |
||||
|
__iter__ = __str__ = __len__ = __nonzero__ = __eq__ = \ |
||||
|
__ne__ = __bool__ = __hash__ = \ |
||||
|
Undefined._fail_with_undefined_error |
||||
|
|
||||
|
|
||||
|
# remove remaining slots attributes, after the metaclass did the magic they |
||||
|
# are unneeded and irritating as they contain wrong data for the subclasses. |
||||
|
del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__ |
@ -0,0 +1,367 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.sandbox |
||||
|
~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Adds a sandbox layer to Jinja as it was the default behavior in the old |
||||
|
Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the |
||||
|
default behavior is easier to use. |
||||
|
|
||||
|
The behavior can be changed by subclassing the environment. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
import types |
||||
|
import operator |
||||
|
from jinja2.environment import Environment |
||||
|
from jinja2.exceptions import SecurityError |
||||
|
from jinja2._compat import string_types, PY2 |
||||
|
|
||||
|
|
||||
|
#: maximum number of items a range may produce |
||||
|
MAX_RANGE = 100000 |
||||
|
|
||||
|
#: attributes of function objects that are considered unsafe. |
||||
|
if PY2: |
||||
|
UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict', |
||||
|
'func_defaults', 'func_globals']) |
||||
|
else: |
||||
|
# On versions > python 2 the special attributes on functions are gone, |
||||
|
# but they remain on methods and generators for whatever reason. |
||||
|
UNSAFE_FUNCTION_ATTRIBUTES = set() |
||||
|
|
||||
|
|
||||
|
#: unsafe method attributes. function attributes are unsafe for methods too |
||||
|
UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self']) |
||||
|
|
||||
|
#: unsafe generator attirbutes. |
||||
|
UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code']) |
||||
|
|
||||
|
import warnings |
||||
|
|
||||
|
# make sure we don't warn in python 2.6 about stuff we don't care about |
||||
|
warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning, |
||||
|
module='jinja2.sandbox') |
||||
|
|
||||
|
from collections import deque |
||||
|
|
||||
|
_mutable_set_types = (set,) |
||||
|
_mutable_mapping_types = (dict,) |
||||
|
_mutable_sequence_types = (list,) |
||||
|
|
||||
|
|
||||
|
# on python 2.x we can register the user collection types |
||||
|
try: |
||||
|
from UserDict import UserDict, DictMixin |
||||
|
from UserList import UserList |
||||
|
_mutable_mapping_types += (UserDict, DictMixin) |
||||
|
_mutable_set_types += (UserList,) |
||||
|
except ImportError: |
||||
|
pass |
||||
|
|
||||
|
# if sets is still available, register the mutable set from there as well |
||||
|
try: |
||||
|
from sets import Set |
||||
|
_mutable_set_types += (Set,) |
||||
|
except ImportError: |
||||
|
pass |
||||
|
|
||||
|
#: register Python 2.6 abstract base classes |
||||
|
try: |
||||
|
from collections import MutableSet, MutableMapping, MutableSequence |
||||
|
_mutable_set_types += (MutableSet,) |
||||
|
_mutable_mapping_types += (MutableMapping,) |
||||
|
_mutable_sequence_types += (MutableSequence,) |
||||
|
except ImportError: |
||||
|
pass |
||||
|
|
||||
|
_mutable_spec = ( |
||||
|
(_mutable_set_types, frozenset([ |
||||
|
'add', 'clear', 'difference_update', 'discard', 'pop', 'remove', |
||||
|
'symmetric_difference_update', 'update' |
||||
|
])), |
||||
|
(_mutable_mapping_types, frozenset([ |
||||
|
'clear', 'pop', 'popitem', 'setdefault', 'update' |
||||
|
])), |
||||
|
(_mutable_sequence_types, frozenset([ |
||||
|
'append', 'reverse', 'insert', 'sort', 'extend', 'remove' |
||||
|
])), |
||||
|
(deque, frozenset([ |
||||
|
'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop', |
||||
|
'popleft', 'remove', 'rotate' |
||||
|
])) |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def safe_range(*args): |
||||
|
"""A range that can't generate ranges with a length of more than |
||||
|
MAX_RANGE items. |
||||
|
""" |
||||
|
rng = range(*args) |
||||
|
if len(rng) > MAX_RANGE: |
||||
|
raise OverflowError('range too big, maximum size for range is %d' % |
||||
|
MAX_RANGE) |
||||
|
return rng |
||||
|
|
||||
|
|
||||
|
def unsafe(f): |
||||
|
"""Marks a function or method as unsafe. |
||||
|
|
||||
|
:: |
||||
|
|
||||
|
@unsafe |
||||
|
def delete(self): |
||||
|
pass |
||||
|
""" |
||||
|
f.unsafe_callable = True |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
def is_internal_attribute(obj, attr): |
||||
|
"""Test if the attribute given is an internal python attribute. For |
||||
|
example this function returns `True` for the `func_code` attribute of |
||||
|
python objects. This is useful if the environment method |
||||
|
:meth:`~SandboxedEnvironment.is_safe_attribute` is overridden. |
||||
|
|
||||
|
>>> from jinja2.sandbox import is_internal_attribute |
||||
|
>>> is_internal_attribute(str, "mro") |
||||
|
True |
||||
|
>>> is_internal_attribute(str, "upper") |
||||
|
False |
||||
|
""" |
||||
|
if isinstance(obj, types.FunctionType): |
||||
|
if attr in UNSAFE_FUNCTION_ATTRIBUTES: |
||||
|
return True |
||||
|
elif isinstance(obj, types.MethodType): |
||||
|
if attr in UNSAFE_FUNCTION_ATTRIBUTES or \ |
||||
|
attr in UNSAFE_METHOD_ATTRIBUTES: |
||||
|
return True |
||||
|
elif isinstance(obj, type): |
||||
|
if attr == 'mro': |
||||
|
return True |
||||
|
elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)): |
||||
|
return True |
||||
|
elif isinstance(obj, types.GeneratorType): |
||||
|
if attr in UNSAFE_GENERATOR_ATTRIBUTES: |
||||
|
return True |
||||
|
return attr.startswith('__') |
||||
|
|
||||
|
|
||||
|
def modifies_known_mutable(obj, attr): |
||||
|
"""This function checks if an attribute on a builtin mutable object |
||||
|
(list, dict, set or deque) would modify it if called. It also supports |
||||
|
the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and |
||||
|
with Python 2.6 onwards the abstract base classes `MutableSet`, |
||||
|
`MutableMapping`, and `MutableSequence`. |
||||
|
|
||||
|
>>> modifies_known_mutable({}, "clear") |
||||
|
True |
||||
|
>>> modifies_known_mutable({}, "keys") |
||||
|
False |
||||
|
>>> modifies_known_mutable([], "append") |
||||
|
True |
||||
|
>>> modifies_known_mutable([], "index") |
||||
|
False |
||||
|
|
||||
|
If called with an unsupported object (such as unicode) `False` is |
||||
|
returned. |
||||
|
|
||||
|
>>> modifies_known_mutable("foo", "upper") |
||||
|
False |
||||
|
""" |
||||
|
for typespec, unsafe in _mutable_spec: |
||||
|
if isinstance(obj, typespec): |
||||
|
return attr in unsafe |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
class SandboxedEnvironment(Environment): |
||||
|
"""The sandboxed environment. It works like the regular environment but |
||||
|
tells the compiler to generate sandboxed code. Additionally subclasses of |
||||
|
this environment may override the methods that tell the runtime what |
||||
|
attributes or functions are safe to access. |
||||
|
|
||||
|
If the template tries to access insecure code a :exc:`SecurityError` is |
||||
|
raised. However also other exceptions may occur during the rendering so |
||||
|
the caller has to ensure that all exceptions are caught. |
||||
|
""" |
||||
|
sandboxed = True |
||||
|
|
||||
|
#: default callback table for the binary operators. A copy of this is |
||||
|
#: available on each instance of a sandboxed environment as |
||||
|
#: :attr:`binop_table` |
||||
|
default_binop_table = { |
||||
|
'+': operator.add, |
||||
|
'-': operator.sub, |
||||
|
'*': operator.mul, |
||||
|
'/': operator.truediv, |
||||
|
'//': operator.floordiv, |
||||
|
'**': operator.pow, |
||||
|
'%': operator.mod |
||||
|
} |
||||
|
|
||||
|
#: default callback table for the unary operators. A copy of this is |
||||
|
#: available on each instance of a sandboxed environment as |
||||
|
#: :attr:`unop_table` |
||||
|
default_unop_table = { |
||||
|
'+': operator.pos, |
||||
|
'-': operator.neg |
||||
|
} |
||||
|
|
||||
|
#: a set of binary operators that should be intercepted. Each operator |
||||
|
#: that is added to this set (empty by default) is delegated to the |
||||
|
#: :meth:`call_binop` method that will perform the operator. The default |
||||
|
#: operator callback is specified by :attr:`binop_table`. |
||||
|
#: |
||||
|
#: The following binary operators are interceptable: |
||||
|
#: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**`` |
||||
|
#: |
||||
|
#: The default operation form the operator table corresponds to the |
||||
|
#: builtin function. Intercepted calls are always slower than the native |
||||
|
#: operator call, so make sure only to intercept the ones you are |
||||
|
#: interested in. |
||||
|
#: |
||||
|
#: .. versionadded:: 2.6 |
||||
|
intercepted_binops = frozenset() |
||||
|
|
||||
|
#: a set of unary operators that should be intercepted. Each operator |
||||
|
#: that is added to this set (empty by default) is delegated to the |
||||
|
#: :meth:`call_unop` method that will perform the operator. The default |
||||
|
#: operator callback is specified by :attr:`unop_table`. |
||||
|
#: |
||||
|
#: The following unary operators are interceptable: ``+``, ``-`` |
||||
|
#: |
||||
|
#: The default operation form the operator table corresponds to the |
||||
|
#: builtin function. Intercepted calls are always slower than the native |
||||
|
#: operator call, so make sure only to intercept the ones you are |
||||
|
#: interested in. |
||||
|
#: |
||||
|
#: .. versionadded:: 2.6 |
||||
|
intercepted_unops = frozenset() |
||||
|
|
||||
|
def intercept_unop(self, operator): |
||||
|
"""Called during template compilation with the name of a unary |
||||
|
operator to check if it should be intercepted at runtime. If this |
||||
|
method returns `True`, :meth:`call_unop` is excuted for this unary |
||||
|
operator. The default implementation of :meth:`call_unop` will use |
||||
|
the :attr:`unop_table` dictionary to perform the operator with the |
||||
|
same logic as the builtin one. |
||||
|
|
||||
|
The following unary operators are interceptable: ``+`` and ``-`` |
||||
|
|
||||
|
Intercepted calls are always slower than the native operator call, |
||||
|
so make sure only to intercept the ones you are interested in. |
||||
|
|
||||
|
.. versionadded:: 2.6 |
||||
|
""" |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def __init__(self, *args, **kwargs): |
||||
|
Environment.__init__(self, *args, **kwargs) |
||||
|
self.globals['range'] = safe_range |
||||
|
self.binop_table = self.default_binop_table.copy() |
||||
|
self.unop_table = self.default_unop_table.copy() |
||||
|
|
||||
|
def is_safe_attribute(self, obj, attr, value): |
||||
|
"""The sandboxed environment will call this method to check if the |
||||
|
attribute of an object is safe to access. Per default all attributes |
||||
|
starting with an underscore are considered private as well as the |
||||
|
special attributes of internal python objects as returned by the |
||||
|
:func:`is_internal_attribute` function. |
||||
|
""" |
||||
|
return not (attr.startswith('_') or is_internal_attribute(obj, attr)) |
||||
|
|
||||
|
def is_safe_callable(self, obj): |
||||
|
"""Check if an object is safely callable. Per default a function is |
||||
|
considered safe unless the `unsafe_callable` attribute exists and is |
||||
|
True. Override this method to alter the behavior, but this won't |
||||
|
affect the `unsafe` decorator from this module. |
||||
|
""" |
||||
|
return not (getattr(obj, 'unsafe_callable', False) or |
||||
|
getattr(obj, 'alters_data', False)) |
||||
|
|
||||
|
def call_binop(self, context, operator, left, right): |
||||
|
"""For intercepted binary operator calls (:meth:`intercepted_binops`) |
||||
|
this function is executed instead of the builtin operator. This can |
||||
|
be used to fine tune the behavior of certain operators. |
||||
|
|
||||
|
.. versionadded:: 2.6 |
||||
|
""" |
||||
|
return self.binop_table[operator](left, right) |
||||
|
|
||||
|
def call_unop(self, context, operator, arg): |
||||
|
"""For intercepted unary operator calls (:meth:`intercepted_unops`) |
||||
|
this function is executed instead of the builtin operator. This can |
||||
|
be used to fine tune the behavior of certain operators. |
||||
|
|
||||
|
.. versionadded:: 2.6 |
||||
|
""" |
||||
|
return self.unop_table[operator](arg) |
||||
|
|
||||
|
def getitem(self, obj, argument): |
||||
|
"""Subscribe an object from sandboxed code.""" |
||||
|
try: |
||||
|
return obj[argument] |
||||
|
except (TypeError, LookupError): |
||||
|
if isinstance(argument, string_types): |
||||
|
try: |
||||
|
attr = str(argument) |
||||
|
except Exception: |
||||
|
pass |
||||
|
else: |
||||
|
try: |
||||
|
value = getattr(obj, attr) |
||||
|
except AttributeError: |
||||
|
pass |
||||
|
else: |
||||
|
if self.is_safe_attribute(obj, argument, value): |
||||
|
return value |
||||
|
return self.unsafe_undefined(obj, argument) |
||||
|
return self.undefined(obj=obj, name=argument) |
||||
|
|
||||
|
def getattr(self, obj, attribute): |
||||
|
"""Subscribe an object from sandboxed code and prefer the |
||||
|
attribute. The attribute passed *must* be a bytestring. |
||||
|
""" |
||||
|
try: |
||||
|
value = getattr(obj, attribute) |
||||
|
except AttributeError: |
||||
|
try: |
||||
|
return obj[attribute] |
||||
|
except (TypeError, LookupError): |
||||
|
pass |
||||
|
else: |
||||
|
if self.is_safe_attribute(obj, attribute, value): |
||||
|
return value |
||||
|
return self.unsafe_undefined(obj, attribute) |
||||
|
return self.undefined(obj=obj, name=attribute) |
||||
|
|
||||
|
def unsafe_undefined(self, obj, attribute): |
||||
|
"""Return an undefined object for unsafe attributes.""" |
||||
|
return self.undefined('access to attribute %r of %r ' |
||||
|
'object is unsafe.' % ( |
||||
|
attribute, |
||||
|
obj.__class__.__name__ |
||||
|
), name=attribute, obj=obj, exc=SecurityError) |
||||
|
|
||||
|
def call(__self, __context, __obj, *args, **kwargs): |
||||
|
"""Call an object from sandboxed code.""" |
||||
|
# the double prefixes are to avoid double keyword argument |
||||
|
# errors when proxying the call. |
||||
|
if not __self.is_safe_callable(__obj): |
||||
|
raise SecurityError('%r is not safely callable' % (__obj,)) |
||||
|
return __context.call(__obj, *args, **kwargs) |
||||
|
|
||||
|
|
||||
|
class ImmutableSandboxedEnvironment(SandboxedEnvironment): |
||||
|
"""Works exactly like the regular `SandboxedEnvironment` but does not |
||||
|
permit modifications on the builtin mutable objects `list`, `set`, and |
||||
|
`dict` by using the :func:`modifies_known_mutable` function. |
||||
|
""" |
||||
|
|
||||
|
def is_safe_attribute(self, obj, attr, value): |
||||
|
if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value): |
||||
|
return False |
||||
|
return not modifies_known_mutable(obj, attr) |
@ -0,0 +1,173 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.tests |
||||
|
~~~~~~~~~~~~ |
||||
|
|
||||
|
Jinja test functions. Used with the "is" operator. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
import re |
||||
|
from collections import Mapping |
||||
|
from jinja2.runtime import Undefined |
||||
|
from jinja2._compat import text_type, string_types, integer_types |
||||
|
import decimal |
||||
|
|
||||
|
number_re = re.compile(r'^-?\d+(\.\d+)?$') |
||||
|
regex_type = type(number_re) |
||||
|
|
||||
|
|
||||
|
test_callable = callable |
||||
|
|
||||
|
|
||||
|
def test_odd(value): |
||||
|
"""Return true if the variable is odd.""" |
||||
|
return value % 2 == 1 |
||||
|
|
||||
|
|
||||
|
def test_even(value): |
||||
|
"""Return true if the variable is even.""" |
||||
|
return value % 2 == 0 |
||||
|
|
||||
|
|
||||
|
def test_divisibleby(value, num): |
||||
|
"""Check if a variable is divisible by a number.""" |
||||
|
return value % num == 0 |
||||
|
|
||||
|
|
||||
|
def test_defined(value): |
||||
|
"""Return true if the variable is defined: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{% if variable is defined %} |
||||
|
value of variable: {{ variable }} |
||||
|
{% else %} |
||||
|
variable is not defined |
||||
|
{% endif %} |
||||
|
|
||||
|
See the :func:`default` filter for a simple way to set undefined |
||||
|
variables. |
||||
|
""" |
||||
|
return not isinstance(value, Undefined) |
||||
|
|
||||
|
|
||||
|
def test_undefined(value): |
||||
|
"""Like :func:`defined` but the other way round.""" |
||||
|
return isinstance(value, Undefined) |
||||
|
|
||||
|
|
||||
|
def test_none(value): |
||||
|
"""Return true if the variable is none.""" |
||||
|
return value is None |
||||
|
|
||||
|
|
||||
|
def test_lower(value): |
||||
|
"""Return true if the variable is lowercased.""" |
||||
|
return text_type(value).islower() |
||||
|
|
||||
|
|
||||
|
def test_upper(value): |
||||
|
"""Return true if the variable is uppercased.""" |
||||
|
return text_type(value).isupper() |
||||
|
|
||||
|
|
||||
|
def test_string(value): |
||||
|
"""Return true if the object is a string.""" |
||||
|
return isinstance(value, string_types) |
||||
|
|
||||
|
|
||||
|
def test_mapping(value): |
||||
|
"""Return true if the object is a mapping (dict etc.). |
||||
|
|
||||
|
.. versionadded:: 2.6 |
||||
|
""" |
||||
|
return isinstance(value, Mapping) |
||||
|
|
||||
|
|
||||
|
def test_number(value): |
||||
|
"""Return true if the variable is a number.""" |
||||
|
return isinstance(value, integer_types + (float, complex, decimal.Decimal)) |
||||
|
|
||||
|
|
||||
|
def test_sequence(value): |
||||
|
"""Return true if the variable is a sequence. Sequences are variables |
||||
|
that are iterable. |
||||
|
""" |
||||
|
try: |
||||
|
len(value) |
||||
|
value.__getitem__ |
||||
|
except: |
||||
|
return False |
||||
|
return True |
||||
|
|
||||
|
|
||||
|
def test_equalto(value, other): |
||||
|
"""Check if an object has the same value as another object: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{% if foo.expression is equalto 42 %} |
||||
|
the foo attribute evaluates to the constant 42 |
||||
|
{% endif %} |
||||
|
|
||||
|
This appears to be a useless test as it does exactly the same as the |
||||
|
``==`` operator, but it can be useful when used together with the |
||||
|
`selectattr` function: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{{ users|selectattr("email", "equalto", "foo@bar.invalid") }} |
||||
|
|
||||
|
.. versionadded:: 2.8 |
||||
|
""" |
||||
|
return value == other |
||||
|
|
||||
|
|
||||
|
def test_sameas(value, other): |
||||
|
"""Check if an object points to the same memory address than another |
||||
|
object: |
||||
|
|
||||
|
.. sourcecode:: jinja |
||||
|
|
||||
|
{% if foo.attribute is sameas false %} |
||||
|
the foo attribute really is the `False` singleton |
||||
|
{% endif %} |
||||
|
""" |
||||
|
return value is other |
||||
|
|
||||
|
|
||||
|
def test_iterable(value): |
||||
|
"""Check if it's possible to iterate over an object.""" |
||||
|
try: |
||||
|
iter(value) |
||||
|
except TypeError: |
||||
|
return False |
||||
|
return True |
||||
|
|
||||
|
|
||||
|
def test_escaped(value): |
||||
|
"""Check if the value is escaped.""" |
||||
|
return hasattr(value, '__html__') |
||||
|
|
||||
|
|
||||
|
TESTS = { |
||||
|
'odd': test_odd, |
||||
|
'even': test_even, |
||||
|
'divisibleby': test_divisibleby, |
||||
|
'defined': test_defined, |
||||
|
'undefined': test_undefined, |
||||
|
'none': test_none, |
||||
|
'lower': test_lower, |
||||
|
'upper': test_upper, |
||||
|
'string': test_string, |
||||
|
'mapping': test_mapping, |
||||
|
'number': test_number, |
||||
|
'sequence': test_sequence, |
||||
|
'iterable': test_iterable, |
||||
|
'callable': test_callable, |
||||
|
'sameas': test_sameas, |
||||
|
'equalto': test_equalto, |
||||
|
'escaped': test_escaped |
||||
|
} |
@ -0,0 +1,531 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.utils |
||||
|
~~~~~~~~~~~~ |
||||
|
|
||||
|
Utility functions. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD, see LICENSE for more details. |
||||
|
""" |
||||
|
import re |
||||
|
import errno |
||||
|
from collections import deque |
||||
|
from threading import Lock |
||||
|
from jinja2._compat import text_type, string_types, implements_iterator, \ |
||||
|
url_quote |
||||
|
|
||||
|
|
||||
|
_word_split_re = re.compile(r'(\s+)') |
||||
|
_punctuation_re = re.compile( |
||||
|
'^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( |
||||
|
'|'.join(map(re.escape, ('(', '<', '<'))), |
||||
|
'|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '>'))) |
||||
|
) |
||||
|
) |
||||
|
_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') |
||||
|
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') |
||||
|
_entity_re = re.compile(r'&([^;]+);') |
||||
|
_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
||||
|
_digits = '0123456789' |
||||
|
|
||||
|
# special singleton representing missing values for the runtime |
||||
|
missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})() |
||||
|
|
||||
|
# internal code |
||||
|
internal_code = set() |
||||
|
|
||||
|
concat = u''.join |
||||
|
|
||||
|
|
||||
|
def contextfunction(f): |
||||
|
"""This decorator can be used to mark a function or method context callable. |
||||
|
A context callable is passed the active :class:`Context` as first argument when |
||||
|
called from the template. This is useful if a function wants to get access |
||||
|
to the context or functions provided on the context object. For example |
||||
|
a function that returns a sorted list of template variables the current |
||||
|
template exports could look like this:: |
||||
|
|
||||
|
@contextfunction |
||||
|
def get_exported_names(context): |
||||
|
return sorted(context.exported_vars) |
||||
|
""" |
||||
|
f.contextfunction = True |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
def evalcontextfunction(f): |
||||
|
"""This decorator can be used to mark a function or method as an eval |
||||
|
context callable. This is similar to the :func:`contextfunction` |
||||
|
but instead of passing the context, an evaluation context object is |
||||
|
passed. For more information about the eval context, see |
||||
|
:ref:`eval-context`. |
||||
|
|
||||
|
.. versionadded:: 2.4 |
||||
|
""" |
||||
|
f.evalcontextfunction = True |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
def environmentfunction(f): |
||||
|
"""This decorator can be used to mark a function or method as environment |
||||
|
callable. This decorator works exactly like the :func:`contextfunction` |
||||
|
decorator just that the first argument is the active :class:`Environment` |
||||
|
and not context. |
||||
|
""" |
||||
|
f.environmentfunction = True |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
def internalcode(f): |
||||
|
"""Marks the function as internally used""" |
||||
|
internal_code.add(f.__code__) |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
def is_undefined(obj): |
||||
|
"""Check if the object passed is undefined. This does nothing more than |
||||
|
performing an instance check against :class:`Undefined` but looks nicer. |
||||
|
This can be used for custom filters or tests that want to react to |
||||
|
undefined variables. For example a custom default filter can look like |
||||
|
this:: |
||||
|
|
||||
|
def default(var, default=''): |
||||
|
if is_undefined(var): |
||||
|
return default |
||||
|
return var |
||||
|
""" |
||||
|
from jinja2.runtime import Undefined |
||||
|
return isinstance(obj, Undefined) |
||||
|
|
||||
|
|
||||
|
def consume(iterable): |
||||
|
"""Consumes an iterable without doing anything with it.""" |
||||
|
for event in iterable: |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
def clear_caches(): |
||||
|
"""Jinja2 keeps internal caches for environments and lexers. These are |
||||
|
used so that Jinja2 doesn't have to recreate environments and lexers all |
||||
|
the time. Normally you don't have to care about that but if you are |
||||
|
messuring memory consumption you may want to clean the caches. |
||||
|
""" |
||||
|
from jinja2.environment import _spontaneous_environments |
||||
|
from jinja2.lexer import _lexer_cache |
||||
|
_spontaneous_environments.clear() |
||||
|
_lexer_cache.clear() |
||||
|
|
||||
|
|
||||
|
def import_string(import_name, silent=False): |
||||
|
"""Imports an object based on a string. This is useful if you want to |
||||
|
use import paths as endpoints or something similar. An import path can |
||||
|
be specified either in dotted notation (``xml.sax.saxutils.escape``) |
||||
|
or with a colon as object delimiter (``xml.sax.saxutils:escape``). |
||||
|
|
||||
|
If the `silent` is True the return value will be `None` if the import |
||||
|
fails. |
||||
|
|
||||
|
:return: imported object |
||||
|
""" |
||||
|
try: |
||||
|
if ':' in import_name: |
||||
|
module, obj = import_name.split(':', 1) |
||||
|
elif '.' in import_name: |
||||
|
items = import_name.split('.') |
||||
|
module = '.'.join(items[:-1]) |
||||
|
obj = items[-1] |
||||
|
else: |
||||
|
return __import__(import_name) |
||||
|
return getattr(__import__(module, None, None, [obj]), obj) |
||||
|
except (ImportError, AttributeError): |
||||
|
if not silent: |
||||
|
raise |
||||
|
|
||||
|
|
||||
|
def open_if_exists(filename, mode='rb'): |
||||
|
"""Returns a file descriptor for the filename if that file exists, |
||||
|
otherwise `None`. |
||||
|
""" |
||||
|
try: |
||||
|
return open(filename, mode) |
||||
|
except IOError as e: |
||||
|
if e.errno not in (errno.ENOENT, errno.EISDIR, errno.EINVAL): |
||||
|
raise |
||||
|
|
||||
|
|
||||
|
def object_type_repr(obj): |
||||
|
"""Returns the name of the object's type. For some recognized |
||||
|
singletons the name of the object is returned instead. (For |
||||
|
example for `None` and `Ellipsis`). |
||||
|
""" |
||||
|
if obj is None: |
||||
|
return 'None' |
||||
|
elif obj is Ellipsis: |
||||
|
return 'Ellipsis' |
||||
|
# __builtin__ in 2.x, builtins in 3.x |
||||
|
if obj.__class__.__module__ in ('__builtin__', 'builtins'): |
||||
|
name = obj.__class__.__name__ |
||||
|
else: |
||||
|
name = obj.__class__.__module__ + '.' + obj.__class__.__name__ |
||||
|
return '%s object' % name |
||||
|
|
||||
|
|
||||
|
def pformat(obj, verbose=False): |
||||
|
"""Prettyprint an object. Either use the `pretty` library or the |
||||
|
builtin `pprint`. |
||||
|
""" |
||||
|
try: |
||||
|
from pretty import pretty |
||||
|
return pretty(obj, verbose=verbose) |
||||
|
except ImportError: |
||||
|
from pprint import pformat |
||||
|
return pformat(obj) |
||||
|
|
||||
|
|
||||
|
def urlize(text, trim_url_limit=None, nofollow=False, target=None): |
||||
|
"""Converts any URLs in text into clickable links. Works on http://, |
||||
|
https:// and www. links. Links can have trailing punctuation (periods, |
||||
|
commas, close-parens) and leading punctuation (opening parens) and |
||||
|
it'll still do the right thing. |
||||
|
|
||||
|
If trim_url_limit is not None, the URLs in link text will be limited |
||||
|
to trim_url_limit characters. |
||||
|
|
||||
|
If nofollow is True, the URLs in link text will get a rel="nofollow" |
||||
|
attribute. |
||||
|
|
||||
|
If target is not None, a target attribute will be added to the link. |
||||
|
""" |
||||
|
trim_url = lambda x, limit=trim_url_limit: limit is not None \ |
||||
|
and (x[:limit] + (len(x) >=limit and '...' |
||||
|
or '')) or x |
||||
|
words = _word_split_re.split(text_type(escape(text))) |
||||
|
nofollow_attr = nofollow and ' rel="nofollow"' or '' |
||||
|
if target is not None and isinstance(target, string_types): |
||||
|
target_attr = ' target="%s"' % target |
||||
|
else: |
||||
|
target_attr = '' |
||||
|
for i, word in enumerate(words): |
||||
|
match = _punctuation_re.match(word) |
||||
|
if match: |
||||
|
lead, middle, trail = match.groups() |
||||
|
if middle.startswith('www.') or ( |
||||
|
'@' not in middle and |
||||
|
not middle.startswith('http://') and |
||||
|
not middle.startswith('https://') and |
||||
|
len(middle) > 0 and |
||||
|
middle[0] in _letters + _digits and ( |
||||
|
middle.endswith('.org') or |
||||
|
middle.endswith('.net') or |
||||
|
middle.endswith('.com') |
||||
|
)): |
||||
|
middle = '<a href="http://%s"%s%s>%s</a>' % (middle, |
||||
|
nofollow_attr, target_attr, trim_url(middle)) |
||||
|
if middle.startswith('http://') or \ |
||||
|
middle.startswith('https://'): |
||||
|
middle = '<a href="%s"%s%s>%s</a>' % (middle, |
||||
|
nofollow_attr, target_attr, trim_url(middle)) |
||||
|
if '@' in middle and not middle.startswith('www.') and \ |
||||
|
not ':' in middle and _simple_email_re.match(middle): |
||||
|
middle = '<a href="mailto:%s">%s</a>' % (middle, middle) |
||||
|
if lead + middle + trail != word: |
||||
|
words[i] = lead + middle + trail |
||||
|
return u''.join(words) |
||||
|
|
||||
|
|
||||
|
def generate_lorem_ipsum(n=5, html=True, min=20, max=100): |
||||
|
"""Generate some lorem ipsum for the template.""" |
||||
|
from jinja2.constants import LOREM_IPSUM_WORDS |
||||
|
from random import choice, randrange |
||||
|
words = LOREM_IPSUM_WORDS.split() |
||||
|
result = [] |
||||
|
|
||||
|
for _ in range(n): |
||||
|
next_capitalized = True |
||||
|
last_comma = last_fullstop = 0 |
||||
|
word = None |
||||
|
last = None |
||||
|
p = [] |
||||
|
|
||||
|
# each paragraph contains out of 20 to 100 words. |
||||
|
for idx, _ in enumerate(range(randrange(min, max))): |
||||
|
while True: |
||||
|
word = choice(words) |
||||
|
if word != last: |
||||
|
last = word |
||||
|
break |
||||
|
if next_capitalized: |
||||
|
word = word.capitalize() |
||||
|
next_capitalized = False |
||||
|
# add commas |
||||
|
if idx - randrange(3, 8) > last_comma: |
||||
|
last_comma = idx |
||||
|
last_fullstop += 2 |
||||
|
word += ',' |
||||
|
# add end of sentences |
||||
|
if idx - randrange(10, 20) > last_fullstop: |
||||
|
last_comma = last_fullstop = idx |
||||
|
word += '.' |
||||
|
next_capitalized = True |
||||
|
p.append(word) |
||||
|
|
||||
|
# ensure that the paragraph ends with a dot. |
||||
|
p = u' '.join(p) |
||||
|
if p.endswith(','): |
||||
|
p = p[:-1] + '.' |
||||
|
elif not p.endswith('.'): |
||||
|
p += '.' |
||||
|
result.append(p) |
||||
|
|
||||
|
if not html: |
||||
|
return u'\n\n'.join(result) |
||||
|
return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) |
||||
|
|
||||
|
|
||||
|
def unicode_urlencode(obj, charset='utf-8', for_qs=False): |
||||
|
"""URL escapes a single bytestring or unicode string with the |
||||
|
given charset if applicable to URL safe quoting under all rules |
||||
|
that need to be considered under all supported Python versions. |
||||
|
|
||||
|
If non strings are provided they are converted to their unicode |
||||
|
representation first. |
||||
|
""" |
||||
|
if not isinstance(obj, string_types): |
||||
|
obj = text_type(obj) |
||||
|
if isinstance(obj, text_type): |
||||
|
obj = obj.encode(charset) |
||||
|
safe = not for_qs and b'/' or b'' |
||||
|
rv = text_type(url_quote(obj, safe)) |
||||
|
if for_qs: |
||||
|
rv = rv.replace('%20', '+') |
||||
|
return rv |
||||
|
|
||||
|
|
||||
|
class LRUCache(object): |
||||
|
"""A simple LRU Cache implementation.""" |
||||
|
|
||||
|
# this is fast for small capacities (something below 1000) but doesn't |
||||
|
# scale. But as long as it's only used as storage for templates this |
||||
|
# won't do any harm. |
||||
|
|
||||
|
def __init__(self, capacity): |
||||
|
self.capacity = capacity |
||||
|
self._mapping = {} |
||||
|
self._queue = deque() |
||||
|
self._postinit() |
||||
|
|
||||
|
def _postinit(self): |
||||
|
# alias all queue methods for faster lookup |
||||
|
self._popleft = self._queue.popleft |
||||
|
self._pop = self._queue.pop |
||||
|
self._remove = self._queue.remove |
||||
|
self._wlock = Lock() |
||||
|
self._append = self._queue.append |
||||
|
|
||||
|
def __getstate__(self): |
||||
|
return { |
||||
|
'capacity': self.capacity, |
||||
|
'_mapping': self._mapping, |
||||
|
'_queue': self._queue |
||||
|
} |
||||
|
|
||||
|
def __setstate__(self, d): |
||||
|
self.__dict__.update(d) |
||||
|
self._postinit() |
||||
|
|
||||
|
def __getnewargs__(self): |
||||
|
return (self.capacity,) |
||||
|
|
||||
|
def copy(self): |
||||
|
"""Return a shallow copy of the instance.""" |
||||
|
rv = self.__class__(self.capacity) |
||||
|
rv._mapping.update(self._mapping) |
||||
|
rv._queue = deque(self._queue) |
||||
|
return rv |
||||
|
|
||||
|
def get(self, key, default=None): |
||||
|
"""Return an item from the cache dict or `default`""" |
||||
|
try: |
||||
|
return self[key] |
||||
|
except KeyError: |
||||
|
return default |
||||
|
|
||||
|
def setdefault(self, key, default=None): |
||||
|
"""Set `default` if the key is not in the cache otherwise |
||||
|
leave unchanged. Return the value of this key. |
||||
|
""" |
||||
|
self._wlock.acquire() |
||||
|
try: |
||||
|
try: |
||||
|
return self[key] |
||||
|
except KeyError: |
||||
|
self[key] = default |
||||
|
return default |
||||
|
finally: |
||||
|
self._wlock.release() |
||||
|
|
||||
|
def clear(self): |
||||
|
"""Clear the cache.""" |
||||
|
self._wlock.acquire() |
||||
|
try: |
||||
|
self._mapping.clear() |
||||
|
self._queue.clear() |
||||
|
finally: |
||||
|
self._wlock.release() |
||||
|
|
||||
|
def __contains__(self, key): |
||||
|
"""Check if a key exists in this cache.""" |
||||
|
return key in self._mapping |
||||
|
|
||||
|
def __len__(self): |
||||
|
"""Return the current size of the cache.""" |
||||
|
return len(self._mapping) |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<%s %r>' % ( |
||||
|
self.__class__.__name__, |
||||
|
self._mapping |
||||
|
) |
||||
|
|
||||
|
def __getitem__(self, key): |
||||
|
"""Get an item from the cache. Moves the item up so that it has the |
||||
|
highest priority then. |
||||
|
|
||||
|
Raise a `KeyError` if it does not exist. |
||||
|
""" |
||||
|
self._wlock.acquire() |
||||
|
try: |
||||
|
rv = self._mapping[key] |
||||
|
if self._queue[-1] != key: |
||||
|
try: |
||||
|
self._remove(key) |
||||
|
except ValueError: |
||||
|
# if something removed the key from the container |
||||
|
# when we read, ignore the ValueError that we would |
||||
|
# get otherwise. |
||||
|
pass |
||||
|
self._append(key) |
||||
|
return rv |
||||
|
finally: |
||||
|
self._wlock.release() |
||||
|
|
||||
|
def __setitem__(self, key, value): |
||||
|
"""Sets the value for an item. Moves the item up so that it |
||||
|
has the highest priority then. |
||||
|
""" |
||||
|
self._wlock.acquire() |
||||
|
try: |
||||
|
if key in self._mapping: |
||||
|
self._remove(key) |
||||
|
elif len(self._mapping) == self.capacity: |
||||
|
del self._mapping[self._popleft()] |
||||
|
self._append(key) |
||||
|
self._mapping[key] = value |
||||
|
finally: |
||||
|
self._wlock.release() |
||||
|
|
||||
|
def __delitem__(self, key): |
||||
|
"""Remove an item from the cache dict. |
||||
|
Raise a `KeyError` if it does not exist. |
||||
|
""" |
||||
|
self._wlock.acquire() |
||||
|
try: |
||||
|
del self._mapping[key] |
||||
|
try: |
||||
|
self._remove(key) |
||||
|
except ValueError: |
||||
|
# __getitem__ is not locked, it might happen |
||||
|
pass |
||||
|
finally: |
||||
|
self._wlock.release() |
||||
|
|
||||
|
def items(self): |
||||
|
"""Return a list of items.""" |
||||
|
result = [(key, self._mapping[key]) for key in list(self._queue)] |
||||
|
result.reverse() |
||||
|
return result |
||||
|
|
||||
|
def iteritems(self): |
||||
|
"""Iterate over all items.""" |
||||
|
return iter(self.items()) |
||||
|
|
||||
|
def values(self): |
||||
|
"""Return a list of all values.""" |
||||
|
return [x[1] for x in self.items()] |
||||
|
|
||||
|
def itervalue(self): |
||||
|
"""Iterate over all values.""" |
||||
|
return iter(self.values()) |
||||
|
|
||||
|
def keys(self): |
||||
|
"""Return a list of all keys ordered by most recent usage.""" |
||||
|
return list(self) |
||||
|
|
||||
|
def iterkeys(self): |
||||
|
"""Iterate over all keys in the cache dict, ordered by |
||||
|
the most recent usage. |
||||
|
""" |
||||
|
return reversed(tuple(self._queue)) |
||||
|
|
||||
|
__iter__ = iterkeys |
||||
|
|
||||
|
def __reversed__(self): |
||||
|
"""Iterate over the values in the cache dict, oldest items |
||||
|
coming first. |
||||
|
""" |
||||
|
return iter(tuple(self._queue)) |
||||
|
|
||||
|
__copy__ = copy |
||||
|
|
||||
|
|
||||
|
# register the LRU cache as mutable mapping if possible |
||||
|
try: |
||||
|
from collections import MutableMapping |
||||
|
MutableMapping.register(LRUCache) |
||||
|
except ImportError: |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
@implements_iterator |
||||
|
class Cycler(object): |
||||
|
"""A cycle helper for templates.""" |
||||
|
|
||||
|
def __init__(self, *items): |
||||
|
if not items: |
||||
|
raise RuntimeError('at least one item has to be provided') |
||||
|
self.items = items |
||||
|
self.reset() |
||||
|
|
||||
|
def reset(self): |
||||
|
"""Resets the cycle.""" |
||||
|
self.pos = 0 |
||||
|
|
||||
|
@property |
||||
|
def current(self): |
||||
|
"""Returns the current item.""" |
||||
|
return self.items[self.pos] |
||||
|
|
||||
|
def __next__(self): |
||||
|
"""Goes one item ahead and returns it.""" |
||||
|
rv = self.current |
||||
|
self.pos = (self.pos + 1) % len(self.items) |
||||
|
return rv |
||||
|
|
||||
|
|
||||
|
class Joiner(object): |
||||
|
"""A joining helper for templates.""" |
||||
|
|
||||
|
def __init__(self, sep=u', '): |
||||
|
self.sep = sep |
||||
|
self.used = False |
||||
|
|
||||
|
def __call__(self): |
||||
|
if not self.used: |
||||
|
self.used = True |
||||
|
return u'' |
||||
|
return self.sep |
||||
|
|
||||
|
|
||||
|
# Imported here because that's where it was in the past |
||||
|
from markupsafe import Markup, escape, soft_unicode |
@ -0,0 +1,87 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
""" |
||||
|
jinja2.visitor |
||||
|
~~~~~~~~~~~~~~ |
||||
|
|
||||
|
This module implements a visitor for the nodes. |
||||
|
|
||||
|
:copyright: (c) 2010 by the Jinja Team. |
||||
|
:license: BSD. |
||||
|
""" |
||||
|
from jinja2.nodes import Node |
||||
|
|
||||
|
|
||||
|
class NodeVisitor(object): |
||||
|
"""Walks the abstract syntax tree and call visitor functions for every |
||||
|
node found. The visitor functions may return values which will be |
||||
|
forwarded by the `visit` method. |
||||
|
|
||||
|
Per default the visitor functions for the nodes are ``'visit_'`` + |
||||
|
class name of the node. So a `TryFinally` node visit function would |
||||
|
be `visit_TryFinally`. This behavior can be changed by overriding |
||||
|
the `get_visitor` function. If no visitor function exists for a node |
||||
|
(return value `None`) the `generic_visit` visitor is used instead. |
||||
|
""" |
||||
|
|
||||
|
def get_visitor(self, node): |
||||
|
"""Return the visitor function for this node or `None` if no visitor |
||||
|
exists for this node. In that case the generic visit function is |
||||
|
used instead. |
||||
|
""" |
||||
|
method = 'visit_' + node.__class__.__name__ |
||||
|
return getattr(self, method, None) |
||||
|
|
||||
|
def visit(self, node, *args, **kwargs): |
||||
|
"""Visit a node.""" |
||||
|
f = self.get_visitor(node) |
||||
|
if f is not None: |
||||
|
return f(node, *args, **kwargs) |
||||
|
return self.generic_visit(node, *args, **kwargs) |
||||
|
|
||||
|
def generic_visit(self, node, *args, **kwargs): |
||||
|
"""Called if no explicit visitor function exists for a node.""" |
||||
|
for node in node.iter_child_nodes(): |
||||
|
self.visit(node, *args, **kwargs) |
||||
|
|
||||
|
|
||||
|
class NodeTransformer(NodeVisitor): |
||||
|
"""Walks the abstract syntax tree and allows modifications of nodes. |
||||
|
|
||||
|
The `NodeTransformer` will walk the AST and use the return value of the |
||||
|
visitor functions to replace or remove the old node. If the return |
||||
|
value of the visitor function is `None` the node will be removed |
||||
|
from the previous location otherwise it's replaced with the return |
||||
|
value. The return value may be the original node in which case no |
||||
|
replacement takes place. |
||||
|
""" |
||||
|
|
||||
|
def generic_visit(self, node, *args, **kwargs): |
||||
|
for field, old_value in node.iter_fields(): |
||||
|
if isinstance(old_value, list): |
||||
|
new_values = [] |
||||
|
for value in old_value: |
||||
|
if isinstance(value, Node): |
||||
|
value = self.visit(value, *args, **kwargs) |
||||
|
if value is None: |
||||
|
continue |
||||
|
elif not isinstance(value, Node): |
||||
|
new_values.extend(value) |
||||
|
continue |
||||
|
new_values.append(value) |
||||
|
old_value[:] = new_values |
||||
|
elif isinstance(old_value, Node): |
||||
|
new_node = self.visit(old_value, *args, **kwargs) |
||||
|
if new_node is None: |
||||
|
delattr(node, field) |
||||
|
else: |
||||
|
setattr(node, field, new_node) |
||||
|
return node |
||||
|
|
||||
|
def visit_list(self, node, *args, **kwargs): |
||||
|
"""As transformers may return lists in some places this method |
||||
|
can be used to enforce a list as return value. |
||||
|
""" |
||||
|
rv = self.visit(node, *args, **kwargs) |
||||
|
if not isinstance(rv, list): |
||||
|
rv = [rv] |
||||
|
return rv |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue