#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'autumn'
+pygments_style = 'jinjaext.JinjaStyle'
# Options for HTML output
]
# Additional stuff for the LaTeX preamble.
-latex_preamble = ''
+latex_preamble = '''
+\usepackage{palatino}
+\definecolor{TitleColor}{rgb}{0.7,0,0}
+\definecolor{InnerLinkColor}{rgb}{0.7,0,0}
+\definecolor{OuterLinkColor}{rgb}{0.8,0,0}
+\definecolor{VerbatimColor}{rgb}{0.98,0.98,0.98}
+\definecolor{VerbatimBorderColor}{rgb}{0.8,0.8,0.8}
+'''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
:copyright: Copyright 2008 by Armin Ronacher.
:license: BSD.
"""
+import re
import inspect
+from types import BuiltinFunctionType
from docutils import nodes
from docutils.statemachine import ViewList
from sphinx.ext.autodoc import prepare_docstring
-def format_filter(name, aliases, func):
- try:
- argspec = inspect.getargspec(func)
- except:
+from pygments.style import Style
+from pygments.token import Keyword, Name, Comment, String, Error, \
+ Number, Operator, Generic
+
+
+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: '#333333',
+
+ 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: '#BE9B5D',
+ Number: '#FF0000',
+
+ 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.__init__)
+ argspec = inspect.getargspec(func)
+ if getattr(func, 'environmentfilter', False) or \
+ getattr(func, 'contextfilter', False):
+ del argspec[0][0]
+ signature = inspect.formatargspec(*argspec)
except:
- try:
- argspec = inspect.getargspec(func.__new__)
- except:
- return []
- del argspec[0][0]
- if getattr(func, 'environmentfilter', False) or \
- getattr(func, 'contextfilter', False):
- del argspec[0][0]
- signature = inspect.formatargspec(*argspec)
+ pass
result = ['.. function:: %s%s' % (name, signature), '']
- for line in inspect.getdoc(func).splitlines():
- result.append(' ' + line)
+ result.extend(' ' + line for line in lines)
if aliases:
result.extend(('', ' :aliases: %s' % ', '.join(
'``%s``' % x for x in sorted(aliases))))
return result
-def jinja_filters(dirname, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- from jinja2.defaults import DEFAULT_FILTERS
- mapping = {}
- for name, func in DEFAULT_FILTERS.iteritems():
- mapping.setdefault(func, []).append(name)
- filters = []
- for func, names in mapping.iteritems():
- aliases = sorted(names, key=lambda x: len(x))
- name = aliases.pop()
- filters.append((name, aliases, func))
- filters.sort()
+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.iteritems():
+ reverse_mapping.setdefault(func, []).append(name)
+ filters = []
+ for func, names in reverse_mapping.iteritems():
+ 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
- result = ViewList()
- for name, aliases, func in filters:
- for item in format_filter(name, aliases, func):
- result.append(item, '<jinjaext>')
- node = nodes.paragraph()
- state.nested_parse(result, content_offset, node)
- return node.children
+from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS
+jinja_filters = dump_functions(DEFAULT_FILTERS)
+jinja_tests = dump_functions(DEFAULT_TESTS)
def setup(app):
- app.add_directive('jinjafilters', jinja_filters, 1, (0, 0, 0))
+ app.add_directive('jinjafilters', jinja_filters, 0, (0, 0, 0))
+ app.add_directive('jinjatests', jinja_tests, 0, (0, 0, 0))
around the arguments, like a function call. This example will join a list
by spaces: ``{{ list|join(', ') }}``.
-The `builtin-filters`_ below describes all the builtin filters.
+The :ref:`builtin-filters` below describes all the builtin filters.
Tests
{% if loop.index is divisibleby 3 %}
{% if loop.index is divisibleby(3) %}
-The `builtin-tests`_ below descibes all the builtin tests.
+The :ref:`builtin-tests` below descibes all the builtin tests.
Comments
escaped HTML.
Working with Manual Escaping
-----------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If manual escaping is enabled it's **your** responsibility to escape
variables if needed. What to escape? If you have a variable that *may*
``{{ user.username|e }}``.
Working with Automatic Escaping
--------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When automatic escaping is enabled everything is escaped by default except
for values explicitly marked as safe. Those can either be marked by the
application or in the template by using the `|safe` filter. The main
-problem with this approach is that python itself doesn't have the concept
+problem with this approach is that Python itself doesn't have the concept
of tainted values so the information if a value is safe or unsafe can get
lost. If the information is lost escaping will take place which means that
you could end up with double escaped contents.
Double escaping is easy to avoid however, just relay on the tools Jinja2
-provides and don't use builtin python constructs such as the string modulo
+provides and don't use builtin Python constructs such as the string modulo
operator.
Functions returning template data (macros, `super`, `self.BLOCKNAME`) return
safe markup always.
String literals in templates with automatic escaping are considered unsafe
-too. The reason for this is that the safe string is an extension to python
+too. The reason for this is that the safe string is an extension to Python
and not every library will work properly with it.
+List of Control Structures
+--------------------------
+
+A control structure refers to all those things that control the flow of a
+program - conditionals (i.e. if/elif/else), for-loops, as well as things like
+macros and blocks. Control structures appear inside ``{% ... %}`` blocks
+in the default syntax.
+
+For Loops
+~~~~~~~~~
+
+Loop over each item in a sequece. For example, to display a list of users
+provided in a variable called `users`:
+
+.. sourcecode:: html+jinja
+
+ <h1>Members</h1>
+ <ul>
+ {% for user in users %}
+ <li>{{ user.username|e }}</li>
+ {% endfor %}
+ </ul>
+
+Inside of a for loop block you can access some special variables:
+
++-----------------------+---------------------------------------------------+
+| `loop.index` | The current iteration of the loop. (1 indexed) |
++-----------------------+---------------------------------------------------+
+| `loop.index0` | The current iteration of the loop. (0 indexed) |
++-----------------------+---------------------------------------------------+
+| `loop.revindex` | The number of iterations from the end of the loop |
+| | (1 indexed) |
++-----------------------+---------------------------------------------------+
+| `loop.revindex0` | The number of iterations from the end of the loop |
+| | (0 indexed) |
++-----------------------+---------------------------------------------------+
+| `loop.first` | True if first iteration. |
++-----------------------+---------------------------------------------------+
+| `loop.last` | True if last iteration. |
++-----------------------+---------------------------------------------------+
+| `loop.length` | The number of items in the sequence. |
++-----------------------+---------------------------------------------------+
+| `loop.cycle` | A helper function to cycle between a list of |
+| | sequences. See the explanation below. |
++-----------------------+---------------------------------------------------+
+
+Within a for-loop, it's psosible to cycle among a list of strings/variables
+each time through the loop by using the special `loop.cycle` helper:
+
+.. sourcecode:: html+jinja
+
+ {% for row in rows %}
+ <li class="{{ loop.cycle('odd', 'even') }}">{{ row }}</li>
+ {% endfor %}
+
+Unlike in Python it's not possible to `break` or `continue` in a loop. You
+can however filter the sequence during iteration which allows you to skip
+items. The following example skips all the users which are hidden:
+
+.. sourcecode:: html+jinja
+
+ {% for user in users if not user.hidden %}
+ <li>{{ user.username|e }}</li>
+ {% endfor %}
+
+The advantage is that the special `loop` variable will count correctly thus
+not counting the users not iterated over.
+
+If no iteration took place because the sequence was empty or the filtering
+removed all the items from the sequence you can render a replacement block
+by using `else`:
+
+.. sourcecode:: html+jinja
+
+ <ul>
+ {% for user in users %}
+ <li>{{ user.username|e }}</li>
+ {% else %}
+ <li><em>no users found</em></li>
+ {% endif %}
+ </ul>
+
+
.. _builtin-filters:
List of Builtin Filters
List of Builtin Tests
---------------------
-bleh
+.. jinjatests::
@environmentfilter
-def do_xmlattr(_environment, *args, **kwargs):
+def do_xmlattr(_environment, 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:
</ul>
As you can see it automatically prepends a space in front of the item
- if the filter returned something.
+ 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 dict(*args, **kwargs).iteritems()
if value is not None and not isinstance(value, Undefined)
)
- if rv:
+ if autospace and rv:
rv = u' ' + rv
if _environment.autoescape:
rv = Markup(rv)
def do_indent(s, width=4, indentfirst=False):
- """
- {{ s|indent[ width[ indentfirst[ usetab]]] }}
-
- Return a copy of the passed string, each line indented by
+ """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:
def do_truncate(s, length=255, killwords=False, end='...'):
- """
- Return a truncated copy of the string. The length is specified
+ """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 try to save the last word. If the text was in fact
return tuple.__new__(cls, (key, list(value)))
+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_reverse(value):
+ """Reverse the object or return an iterator the iterates over it the other
+ way round.
+ """
+ if isinstance(value, basestring):
+ 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')
+
+
FILTERS = {
'replace': do_replace,
'upper': do_upper,
'count': len,
'dictsort': do_dictsort,
'length': len,
- 'reverse': reversed,
+ 'reverse': do_reverse,
'center': do_center,
'indent': do_indent,
'title': do_title,
'int': do_int,
'float': do_float,
'string': soft_unicode,
- 'list': list,
+ 'list': do_list,
'urlize': do_urlize,
'format': do_format,
'trim': do_trim,
'round': do_round,
'sort': do_sort,
'groupby': do_groupby,
- 'safe': Markup,
+ 'safe': do_mark_safe,
'xmlattr': do_xmlattr
}
ext = self.extensions.get(self.stream.current.value)
if ext is not None:
return ext(self)
- lineno = self.stream.current
+ lineno = self.stream.current.lineno
expr = self.parse_tuple()
if self.stream.current.type == 'assign':
result = self.parse_assign(expr)