From 37a8851c69709031491d32a8c71468a6c3b65453 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 2 Mar 2007 20:42:18 +0100 Subject: [PATCH] [svn] add first part of jinja documentation --HG-- branch : trunk --- artwork/jinjalogo.svg | 111 +++++++++ docs/build/jinjabanner.png | Bin 0 -> 10539 bytes docs/build/jinjalogo.png | Bin 0 -> 21383 bytes docs/build/style.css | 196 ++++++++++++++++ docs/build/watermark.png | Bin 0 -> 9521 bytes docs/generate.py | 275 +++++++++++++++++++++++ docs/src/designerdoc.txt | 432 ++++++++++++++++++++++++++++++++++++ docs/src/devintro.txt | 139 ++++++++++++ docs/src/fromdjango.txt | 108 +++++++++ docs/src/index.txt | 25 +++ docs/src/loaders.txt | 10 + jinja/__init__.py | 4 +- jinja/datastructure.py | 3 + jinja/environment.py | 6 +- jinja/filters.py | 184 ++++++++------- jinja/loaders.py | 23 +- jinja/nodes.py | 4 +- jinja/tests.py | 49 ++-- jinja/translators/python.py | 2 +- jinja/utils.py | 6 +- 20 files changed, 1444 insertions(+), 133 deletions(-) create mode 100644 artwork/jinjalogo.svg create mode 100644 docs/build/jinjabanner.png create mode 100644 docs/build/jinjalogo.png create mode 100644 docs/build/style.css create mode 100644 docs/build/watermark.png create mode 100755 docs/generate.py create mode 100644 docs/src/designerdoc.txt create mode 100644 docs/src/devintro.txt create mode 100644 docs/src/fromdjango.txt create mode 100644 docs/src/index.txt create mode 100644 docs/src/loaders.txt diff --git a/artwork/jinjalogo.svg b/artwork/jinjalogo.svg new file mode 100644 index 0000000..3eb485f --- /dev/null +++ b/artwork/jinjalogo.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/docs/build/jinjabanner.png b/docs/build/jinjabanner.png new file mode 100644 index 0000000000000000000000000000000000000000..c672118065d1caa3bc52d440aecc99441a252fa8 GIT binary patch literal 10539 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 literal 0 HcmV?d00001 diff --git a/docs/build/jinjalogo.png b/docs/build/jinjalogo.png new file mode 100644 index 0000000000000000000000000000000000000000..17d6dc31eab31a0faacc8cba623fbcce0b726519 GIT binary patch literal 21383 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 F;eR, it's generated by ReST for `x` */ + font-size: 13px; + font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; + font-weight: bold; + font-style: normal; +} + +div.admonition { + margin: 10px 0 10px 0; + padding: 10px; + border: 1px solid #ccc; + background-color: #f8f8f8; +} + +div.admonition p.admonition-title { + margin: -3px 0 5px 0; + font-weight: bold; + color: #b41717; + font-size: 16px; +} + +div.admonition p { + margin: 0 0 0 40px; +} + +#toc { + margin: 0 -10px 10px 15px; + padding: 10px; + width: 200px; + float: right; + background-color: #f8f8f8; + border: 1px solid #ccc; + border-right: none; +} + +#toc h2 { + font-size: 20px; + margin: 0 0 10px 0; + padding: 0; + color: #444; +} + +#toc ul { + margin: 0 0 0 30px; + padding: 0; +} + +#toc ul + h2 { + margin-top: 10px; +} + +#toc ul li { + padding: 0; + margin: 2px 0 2px 0; +} diff --git a/docs/build/watermark.png b/docs/build/watermark.png new file mode 100644 index 0000000000000000000000000000000000000000..297d8990c5a23df6f98f811e183171399904aec2 GIT binary patch literal 9521 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 BPksOZ literal 0 HcmV?d00001 diff --git a/docs/generate.py b/docs/generate.py new file mode 100755 index 0000000..0c155c0 --- /dev/null +++ b/docs/generate.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Generate Jinja Documentation + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Generates a bunch of html files containing the documentation. + + :copyright: 2006-2007 by Armin Ronacher, Georg Brandl. + :license: BSD, see LICENSE for more details. +""" +import os +import sys +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +import re +import inspect +from datetime import datetime +from cgi import escape + +from docutils import nodes +from docutils.parsers.rst import directives +from docutils.core import publish_parts +from docutils.writers import html4css1 + +from jinja import Environment + +from pygments import highlight +from pygments.lexers import get_lexer_by_name +from pygments.formatters import HtmlFormatter + +def generate_list_of_filters(): + from jinja.filters import FILTERS + result = [] + + filters = {} + for name, f in FILTERS.iteritems(): + if not f in filters: + filters[f] = ([name], inspect.getdoc(f)) + else: + filters[f][0].append(name) + for names, _ in filters.itervalues(): + names.sort(key=lambda x: -len(x)) + + for names, doc in sorted(filters.values(), key=lambda x: x[0][0].lower()): + name = names[0] + if len(names) > 1: + aliases = '\n\n :Aliases: %s\n' % ', '.join(names[1:]) + else: + aliases = '' + + doclines = [] + for line in doc.splitlines(): + doclines.append(' ' + line) + doc = '\n'.join(doclines) + result.append('`%s`\n%s%s' % (name, doc, aliases)) + + return '\n'.join(result) + +def generate_list_of_tests(): + from jinja.tests import TESTS + result = [] + + tests = {} + for name, f in TESTS.iteritems(): + if not f in tests: + tests[f] = ([name], inspect.getdoc(f)) + else: + tests[f][0].append(name) + for names, _ in tests.itervalues(): + names.sort(key=lambda x: -len(x)) + + for names, doc in sorted(tests.values(), key=lambda x: x[0][0].lower()): + name = names[0] + if len(names) > 1: + aliases = '\n\n :Aliases: %s\n' % ', '.join(names[1:]) + else: + aliases = '' + + doclines = [] + for line in doc.splitlines(): + doclines.append(' ' + line) + doc = '\n'.join(doclines) + result.append('`%s`\n%s%s' % (name, doc, aliases)) + + return '\n'.join(result) + +def generate_list_of_loaders(): + from jinja import loaders as loader_module + + result = [] + loaders = [] + for item in loader_module.__all__: + loaders.append(getattr(loader_module, item)) + loaders.sort(key=lambda x: x.__name__.lower()) + + for loader in loaders: + doclines = [] + for line in inspect.getdoc(loader).splitlines(): + doclines.append(' ' + line) + result.append('`%s`\n%s' % (loader.__name__, '\n'.join(doclines))) + + return '\n\n'.join(result) + +e = Environment() + +PYGMENTS_FORMATTER = HtmlFormatter(style='pastie', cssclass='syntax') + +LIST_OF_FILTERS = generate_list_of_filters() +LIST_OF_TESTS = generate_list_of_tests() +LIST_OF_LOADERS = generate_list_of_loaders() + +TEMPLATE = e.from_string('''\ + + + + {{ title }} — Jinja Documentation + + + + + + + + +\ +''') + +def pygments_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + try: + lexer = get_lexer_by_name(arguments[0]) + except ValueError: + # no lexer found + lexer = get_lexer_by_name('text') + parsed = highlight(u'\n'.join(content), lexer, PYGMENTS_FORMATTER) + return [nodes.raw('', parsed, format="html")] +pygments_directive.arguments = (1, 0, 1) +pygments_directive.content = 1 +directives.register_directive('sourcecode', pygments_directive) + + +def create_translator(link_style): + class Translator(html4css1.HTMLTranslator): + def visit_reference(self, node): + refuri = node.get('refuri') + if refuri is not None and '/' not in refuri and refuri.endswith('.txt'): + node['refuri'] = link_style(refuri[:-4]) + html4css1.HTMLTranslator.visit_reference(self, node) + return Translator + + +class DocumentationWriter(html4css1.Writer): + + def __init__(self, link_style): + html4css1.Writer.__init__(self) + self.translator_class = create_translator(link_style) + + def translate(self): + html4css1.Writer.translate(self) + # generate table of contents + contents = self.build_contents(self.document) + contents_doc = self.document.copy() + contents_doc.children = contents + contents_visitor = self.translator_class(contents_doc) + contents_doc.walkabout(contents_visitor) + self.parts['toc'] = self._generated_toc + + def build_contents(self, node, level=0): + sections = [] + i = len(node) - 1 + while i >= 0 and isinstance(node[i], nodes.section): + sections.append(node[i]) + i -= 1 + sections.reverse() + toc = [] + for section in sections: + try: + reference = nodes.reference('', '', refid=section['ids'][0], *section[0]) + except IndexError: + continue + ref_id = reference['refid'] + text = escape(reference.astext().encode('utf-8')) + toc.append((ref_id, text)) + + self._generated_toc = [('#%s' % href, caption) for href, caption in toc] + # no further processing + return [] + + +def generate_documentation(data, link_style): + writer = DocumentationWriter(link_style) + data = data.replace('[[list_of_filters]]', LIST_OF_FILTERS)\ + .replace('[[list_of_tests]]', LIST_OF_TESTS)\ + .replace('[[list_of_loaders]]', LIST_OF_LOADERS) + parts = publish_parts( + data, + writer=writer, + settings_overrides={ + 'initial_header_level': 3, + 'field_name_limit': 50, + } + ) + return { + 'title': parts['title'].encode('utf-8'), + 'body': parts['body'].encode('utf-8'), + 'toc': parts['toc'] + } + + +def handle_file(filename, fp, dst): + now = datetime.now() + title = os.path.basename(filename)[:-4] + content = fp.read() + parts = generate_documentation(content, (lambda x: './%s.html' % x)) + result = file(os.path.join(dst, title + '.html'), 'w') + c = dict(parts) + c['style'] = PYGMENTS_FORMATTER.get_style_defs('.syntax') + c['generation_date'] = now + c['file_id'] = title + result.write(TEMPLATE.render(c).encode('utf-8')) + result.close() + + +def run(dst, sources=()): + path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'src')) + if not sources: + sources = [os.path.join(path, fn) for fn in os.listdir(path)] + for fn in sources: + if not os.path.isfile(fn): + continue + print 'Processing %s' % fn + f = open(fn) + try: + handle_file(fn, f, dst) + finally: + f.close() + + +def main(dst='build/', *sources): + return run(os.path.realpath(dst), sources) + + +if __name__ == '__main__': + main(*sys.argv[1:]) diff --git a/docs/src/designerdoc.txt b/docs/src/designerdoc.txt new file mode 100644 index 0000000..c3db752 --- /dev/null +++ b/docs/src/designerdoc.txt @@ -0,0 +1,432 @@ +====================== +Designer Documentation +====================== + +This part of the Jinja documentaton is meant for template designers. + +Basics +====== + +The Jinja template language is designed to strike a balance between content +and application logic. Nevertheless you can use a python like statement +language. You don't have to know how Python works to create Jinja templates, +but if you know it you can use some additional statements you may know from +Python. + +Here is a small example template: + +.. sourcecode:: html+jinja + + + + + My Webpage + + + + +

My Webpage

+ {{ variable }} + + + +This covers the default settings. The application developer might have changed +the syntax from ``{% foo %}`` to ``<% foo %>`` or something similar. This +documentation just covers the default values. + +A variable looks like ``{{ foobar }}`` where foobar is the variable name. Inside +of statements (``{% some content here %}``) variables are just normal names +without the braces around it. In fact ``{{ foobar }}`` is just an alias for +the statement ``{% print foobar %}``. + +Variables are coming from the context provided by the application. Normally there +should be a documentation regarding the context contents but if you want to know +the content of the current context you can add this to your template: + +.. sourcecode:: html+jinja + +
{{ debug()|e }}
+ +A context isn't flat which means that each variable can has subvariables, as long +as it is representable as python data structure. You can access attributes of +a variable by using the dot and brace operators. The following examples show +this: + +.. sourcecode:: jinja + + {{ user.username }} + is the same as + {{ user['username'] }} + you can also use a variable to access an attribute: + {{ users[current_user].username }} + If you have numerical indices you have to use the [] syntax: + {{ users[0].username }} + +Filters +======= + +In the examples above you might have noticed the pipe symbols. Pipe symbols tell +the engine that it has to apply a filter on the variable. Here a small example: + +.. sourcecode:: jinja + + {{ variable|replace('foo', 'bar')|escape }} + +If you like you can also put whitespace between the filters. + +This will look for a variable variable, passes it to the filter replace with the +arguments ``'foo'`` and ``'bar'``, and passes the result to the filter `escape` +that automatically xml escapes the value. The ``e`` filter is an alias for +``escape``. Here the complete list of supported filters: + +[[list_of_filters]] + +Tests +===== + +You can use the `is`-operator to perform tests on a value: + +.. sourcecode:: jinja + + {{ 42 is numeric }} -> true + {{ "foobar" is numeric }} -> false + {{ 'FOO' is upper }} -> true + +Those tests are especially useful if used in `if`-conditions. + +[[list_of_tests]] + +Loops +===== + +To iterate over a sequence you can use the `for`-loop. If basically looks like a +normal python for loop and works pretty much the same: + +.. sourcecode:: html+jinja + +

Members

+
    + {% for user in users %} +
  • {{ loop.index }} / {{ loop.length }} - {{ user.username|escape }}
  • + {% else %} +
  • no users found
  • + {% endfor %} +
+ +The optional ``else`` block is only executed if the template did not iterate +because the sequence was empty. + +Inside of a for loop block you can access some special variables: + ++----------------------+----------------------------------------+ +| Variable | Description | ++======================+========================================+ +| ``loop.index`` | The current iteration of the loop. | ++----------------------+----------------------------------------+ +| ``loop.index0`` | The current iteration of the loop, | +| | starting counting by 0. | ++----------------------+----------------------------------------+ +| ``loop.revindex`` | The number of iterations from the end | +| | of the loop. | ++----------------------+----------------------------------------+ +| ``loop.revindex0`` | The number of iterations from the end | +| | of the loop, starting counting by 0. | ++----------------------+----------------------------------------+ +| ``loop.first`` | True if first iteration. | ++----------------------+----------------------------------------+ +| ``loop.last`` | True if last iteration. | ++----------------------+----------------------------------------+ +| ``loop.even`` | True if current iteration is even. | ++----------------------+----------------------------------------+ +| ``loop.odd`` | True if current iteration is odd. | ++----------------------+----------------------------------------+ +| ``loop.length`` | Total number of items in the sequence. | ++----------------------+----------------------------------------+ +| ``loop.parent`` | The context of the parent loop. | ++----------------------+----------------------------------------+ + +Loops also support recursion. For example you have a sitemap where each item +might have a number of child items. Such a template could look like this: + +.. sourcecode:: html+jinja + +

Sitemap +
    + {% for item in sitemap recursive %} +
  • {{ item.title|e }} + {% if item.children %}
      {{ loop(item.children) }}
    {% endif %}
  • + {% endfor %} +
+ +Now. What happens here? Basically the first thing that is different to a normal +loop is the additional ``recursive`` modifier in the `for`-loop declaration. +It tells the template engine that we want recursion. If recursion is enabled +the special loop variable is callable. If you call it with a sequence it will +automatically render that loop at that position with the new sequence as argument. + +Cycling +======= + +Sometimes you might want to have different classes for each row in a list. For +example to have alternating row colors. You can easily do this by using the +``{% cycle %}`` tag: + +.. sourcecode:: html+jinja + +
    + {% for message in messages %} +
  • {{ message|e }}
  • + {% endfor %} +
+ +Each time Jinja encounters a cycle tag it will evaluate cycle through the list +of given items and return the next one. If you pass it one item jinja assumes +that this item is a sequence from the context and uses this: + +.. sourcecode:: html+jinja + +
  • ...
  • + +Conditions +========== + +Jinja supports python like ``if`` / ``elif`` / ``else`` constructs: + +.. sourcecode:: jinja + + {% if user.active %} + user {{ user.name|e }} is active. + {% elif user.deleted %} + user {{ user.name|e }} was deleted some time ago. + {% else %} + i don't know what's wrong with {{ user.username|e }} + {% endif %} + +If the user is active the first block is rendered. If not and the user was +deleted the second one, in all other cases the third one. + +You can also use comparison operators: + +.. sourcecode:: html+jinja + + {% if amount < 0 %} + {{ amount }} + {% else %} + {{ amount }} + {% endif %} + +.. admonition:: Note + + Of course you can use `or` / `and` and parenthesis to create more complex + conditions but usually the logic is already handled in the application and + you don't have to create such complex constucts in the template code. However + in some situations it might be a good thing to have the abilities to create + them. + +Operators +========= + +Inside ``{{ variable }}`` blocks, `if`-conditions and many other parts you can +can use Expressions. In expressions you can use any of the following operators: + + ======= =================================================================== + ``+`` add the right operand to the left one. + ``{{ 1 + 2 }}`` would return three. + ``-`` substract the right operand from the left one. + ``{{ 1 - 1 }}`` would return zero. + ``/`` divide the right from the left operand. + ``{{ 1 / 2 }}`` would return 0.5 + ``*`` multiply the left operand with the right. + ``{{ 2 * 2}}`` would return 4 + ``**`` raise the left operand to the power of the right + operand. ``{{ 2**3 }}`` would return 8 + ``is`` perform a test on the value. See the section about + tests for more information. + ``|`` apply a filter on the value. See the section about + filters for more information. + ``and`` return true if the left and the right operand is true. + ``or`` return true if the left or the right operand is true. + ``()`` call a callable. ``{{ user.get_username() }}``. Inside of the + parenthesis you can use variables: ``{{ user.get('username') }}``. + ======= =================================================================== + +Note that there is no support for any bit operation or something similar. + +Macros +====== + +If you want to use a partial template on more than one place you might want to +create a macro out of it: + +.. sourcecode:: html+jinja + + {% macro show_user user %} +

    {{ user.name|e }}

    +
    + {{ user.description }} +
    + {% endmacro %} + +Now you can use it from everywhere in the code by passing it an item: + +.. sourcecode:: jinja + + {% for user in users %} + {{ show_user(user) }} + {% endfor %} + +You can also specify more then one value: + +.. sourcecode:: html+jinja + + {% macro show_dialog title, text %} +
    +

    {{ title|e }}

    +
    {{ text|e }}
    +
    + {% endmacro %} + + {{ show_dialog('Warning', 'something went wrong i guess') }} + +Inheritance +=========== + +The most powerful part of Jinja is template inheritance. Template inheritance +allows you to build a base "skeleton" template that contains all the common +elements of your site and defines **blocks** or **markers** that child +templates can override. + +Sounds complicated but is very basic. It's easiest to understand it by starting +with an example. + +Base Template +------------- + +This template, which we'll call ``base.html``, defines a simple HTML skeleton +document that you might use for a simple two-column page. It's the job of +"child" templates to fill the empty blocks with content: + +.. sourcecode:: html+jinja + + + + + + {% block title %}{% endblock %} - My Webpage + {% block html_head %}{% endblock %} + + +
    + {% block content %}{% endblock %} +
    + + + + +In this example, the ``{% block %}`` tags define four blocks that child templates +can fill in. All the ``block`` tag does is to tell the template engine that a +child template may override those portions of the template. + +Child Template +-------------- + +A child template might look like this: + +.. sourcecode:: html+jinja + + {% extends "base.html" %} + {% block title %}Index{% endblock %} + + {% block html_head %} + + {% endblock %} + + {% block content %} +

    Index

    +

    + Welcome on my awsome homepage. +

    + {% endblock %} + +The ``{% extends %}`` tag is the key here. It tells the template engine that +this template "extends" another template. When the template system evaluates +this template, first it locates the parent. + +The filename of the template depends on the template loader. For example the +``FileSystemLoader`` allows you to access other templates by giving the +filename. You can access subdirectory with an slash: + +.. sourcecode:: jinja + + {% extends "layout/default.html" %} + +But this behavior can depend on the application using Jinja. + +Note that since the child template didn't define the ``footer`` block, the +value from the parent template is used instead. + +.. admonition:: Note + + You can't define multiple ``{% block %}`` tags with the same name in the + same template. This limitation exists because a block tag works in "both" + directions. That is, a block tag doesn't just provide a hole to fill - it + also defines the content that fills the hole in the *parent*. If there were + two similarly-named ``{% block %}`` tags in a template, that template's + parent wouldn't know which one of the blocks' content to use. + +Template Inclusion +================== + +You can load another template at a given posiiton using ``{% include %}``. +Usually it's a better idea to use inheritance but if you for example want to +load macros ``include`` works better than ``extends``: + +.. sourcecode:: jinja + + {% include "myhelpers.html" %} + {{ my_helper("foo") }} + +If you define a macro called ``my_helper`` in ``myhelpers.html`` you can now +use it from the template as shown above. + +Filtering Blocks +================ + +Sometimes it could be a good idea to filter a complete block. For example if +you want to escape some html code: + +.. sourcecode:: jinja + + {% filter escape %} + + goes here + + {% endfilter %} + +Of course you can chain filters too. + +Defining Variables +================== + +You can also define variables in the namespace using the ``{% set %}`` tag: + +.. sourcecode:: jinja + + {% set foo = 'foobar' %} + {{ foo }} + +This should ouputput ``foobar``. diff --git a/docs/src/devintro.txt b/docs/src/devintro.txt new file mode 100644 index 0000000..157301c --- /dev/null +++ b/docs/src/devintro.txt @@ -0,0 +1,139 @@ +==================== +Developer Quickstart +==================== + +This part of the documentation shows you how to embedd Jinja into your +application. + +Starting Up +=========== + +Here the quickest way to create a template from a string and render it: + +.. sourcecode:: python + + from jinja import Environment + env = Environment() + tmpl = env.from_string('Hello {{ name }}!') + print tmpl.render(name='John Doe') + +This example should output the following string after execution:: + + Hello John Doe! + +If you receive an error check if you have a typo in your code. If not have +a look at the `installation`_ page for troubleshooting. + +The Environment +=============== + +The core component of Jinja is the `Environment`. It helds important shared +variables like configuration, filters, tests, globals and other stuff. + +Here the possible initialisation parameters: + +=========================== ================================================== +``block_start_string`` * the string marking the begin of a block. this + defaults to ``'{%'``. +``block_end_string`` * the string marking the end of a block. defaults + to ``'%}'``. +``variable_start_string`` * the string marking the begin of a print + statement. defaults to ``'{{'``. +``comment_start_string`` * the string marking the begin of a + comment. defaults to ``'{#'``. +``comment_end_string`` * the string marking the end of a comment. + defaults to ``'#}'``. +``trim_blocks`` * If this is set to ``True`` the first newline + after a block is removed (block, not + variable tag!). Defaults to ``False``. +``auto_escape`` If this is set to ``True`` Jinja will + automatically escape all variables using xml + escaping methods. If you don't want to escape a + string you have to wrap it in a ``Markup`` + object from the ``jinja.datastructure`` module. +``template_charset`` The charset of the templates. Defaults + to ``'utf-8'``. +``charset`` Charset of all string input data. Defaults + to ``'utf-8'``. +``namespace`` Global namespace for all templates. +``loader`` Specify a template loader. +``filters`` dict of filters or the default filters if not + defined. +``tests`` dict of tests of the default tests if not defined. +=========================== ================================================== + +All of this variables except those marked with a star(*) are modifyable after +environment initialisation. + +The environment provides the following useful functions and properties +additional to the initialisation values: + +=========================== ================================================== +``parse(source, filename)`` Parse the sourcecode and return the abstract + syntax tree. This tree of nodes is used by the + `translators`_ to convert the template into + executable source- or bytecode. +``from_string(source)`` Load and parse a template source and translate it + into evaluable python code. This code is wrapped + with in a `Template` class that allows you to + render it. +``get_template(name)`` load a template from a loader. If the template + does not exist you will get a `TemplateNotFound` + exception. +=========================== ================================================== + +There are also some internal functions on the environment used by the template +evaluation code to keep it sandboxed. + +Loading Templates From Files +============================ + +Loading templates from a string is always a bad idea. It doesn't allow template +inheritance and is also slow since it parses and compiles the template again +and again whereas loaders can cache the template code. + +All you have to do is to define a loader and use the `get_template` function. + +.. sourcecode:: python + + from jinja import Environment, FileSystemLoader + env = Environment(loader=FileSystemLoader('templates')) + tmpl = env.get_template('index.html') + print tmpl.render(name='John Doe') + +This tells jinja to look for templates in the ``templates`` folder. It's a +better idea to use an absolute path here though. For a list of supported +loaders or how to write your own, head over to the `loader`_ documentation. + +Adding Filters +============== + +If you want to add additional filters to the environment the best way is to +modify the ``filters`` attribute and not to pass a dict to the environment. +If you pass it a dict it will not include the default filters! + +.. sourcecode:: python + + from mylib import my_cool_filter + env.filters['mycoolfilter'] = my_cool_filter + +Writing filter functions is explained in the `filter development`_ section. + +Adding Tests +============ + +Adding additional tests works analog to filters: + +.. sourcecode:: python + + from mylib import my_cool_test + env.tests['mycooltest'] = my_cool_test + +Writing tests is explained in the `test development`_ section. + + +.. _installation: installation.txt +.. _translators: translators.txt +.. _loader: loaders.txt +.. _filter development: filters.txt +.. _test development: tests.txt diff --git a/docs/src/fromdjango.txt b/docs/src/fromdjango.txt new file mode 100644 index 0000000..71b2068 --- /dev/null +++ b/docs/src/fromdjango.txt @@ -0,0 +1,108 @@ +=============================== +Differences To Django Templates +=============================== + +If you have previously worked with Django templates you should feel very +familiar. In fact most of the syntax elements look and work the same. + +However Jinja provides some more syntax elements covered in the documentation +and some work a bit different. + +Method Calls +============ + +In Django method calls work implicit. With Jinja you have to tell it that you +want to call it. Thus this Django code: + +.. sourcecode:: django + + {% for page in user.get_created_pages %} + ... + {% endfor %} + +will look like this in Jinja: + +.. sourcecode:: jinja + + {% for page in user.get_created_pages() %} + ... + {% endfor %} + +This allows you to pass variables to the function which is also used for +macros and loop recursion, both features that don't exist in Django. + +Conditions +========== + +In Django you can use the following constructs to check for equality: + +.. sourcecode:: django + + {% ifequals foo "bar" %} + ... + {% else %} + ... + {% endifequals %} + +In Jinja you can use the normal ``if`` statement in combination with +operators: + +.. sourcecode:: jinja + + {% if foo == 'bar' %} + ... + {% else %} + ... + {% endif %} + +You can also have multiple ``elif`` branches in your template: + +.. sourcecode:: jinja + + {% if something %} + ... + {% elif otherthing %} + ... + {% elif foothing %} + ... + {% else %} + ... + {% endif %} + +Filter Arguments +================ + +Jinja provides more than one argument for a filter. Also the syntax for argument +passing is different. A template that looks like this in Django: + +.. sourcecode:: django + + {{ items|join:", " }} + +looks like this in jinja: + +.. sourcecode:: jinja + + {{ items|join(', ') }} + +In fact it's a bit more to write but it allows different type of arguments including +variables and more then one of them. + +Tests +===== + +Additionally to filters there also exists tests you can perform using the `is` +operator. Here some examples: + +.. sourcecode:: jinja + + {% if user.user_id is odd %} + {{ user.username|e }} is odd + {% else %} + hmm. {{ user.username|e }} looks pretty normal + {% endif %} + +For a list of supported tests head over to the `syntax reference`_. + + +.. _syntax reference: designerdoc.txt diff --git a/docs/src/index.txt b/docs/src/index.txt new file mode 100644 index 0000000..250a5a3 --- /dev/null +++ b/docs/src/index.txt @@ -0,0 +1,25 @@ +====================== +Documentation Overview +====================== + +Welcome in the Jinja documentation. + +- `Installing Jinja `_ + +- Application Developer Documentation: + + - `Quickstart `_ + + - `Template Loaders `_ + + - `Filter Functions `_ + + - `Test Functions `_ + + - `Translators `_ + +- Template Designer Documentation: + + - `Syntax Reference `_ + + - `Differences To Django `_ diff --git a/docs/src/loaders.txt b/docs/src/loaders.txt new file mode 100644 index 0000000..dbe4df3 --- /dev/null +++ b/docs/src/loaders.txt @@ -0,0 +1,10 @@ +================ +Template Loaders +================ + +This part of the documentation explains how to use and write a template loader. + +Builtin Loaders +=============== + +[[list_of_loaders]] diff --git a/jinja/__init__.py b/jinja/__init__.py index f65931c..be142ee 100644 --- a/jinja/__init__.py +++ b/jinja/__init__.py @@ -7,6 +7,4 @@ :license: BSD, see LICENSE for more details. """ from jinja.environment import Environment -from jinja.loaders import FileSystemLoader - -__all__ = ['Environment', 'FileSystemLoader'] +from jinja.loaders import * diff --git a/jinja/datastructure.py b/jinja/datastructure.py index fed5cfa..d0b6d44 100644 --- a/jinja/datastructure.py +++ b/jinja/datastructure.py @@ -89,6 +89,9 @@ class Markup(unicode): auto_escape option values marked as `Markup` aren't escaped. """ + def __repr__(self): + return 'Markup(%s)' % unicode.__repr__(self) + safe_types = set([Markup, int, long, float]) diff --git a/jinja/environment.py b/jinja/environment.py index ffce6b3..4277f34 100644 --- a/jinja/environment.py +++ b/jinja/environment.py @@ -68,11 +68,11 @@ class Environment(object): Get or set the template loader. """ self._loader = LoaderWrapper(self, value) - loader = property(lambda s: s._loader, loader, loader.__doc__) + loader = property(lambda s: s._loader.loader, loader, loader.__doc__) - def parse(self, source): + def parse(self, source, filename=None): """Function that creates a new parser and parses the source.""" - parser = Parser(self, source) + parser = Parser(self, source, filename) return parser.parse() def from_string(self, source): diff --git a/jinja/filters.py b/jinja/filters.py index e45849d..d1ecc5d 100644 --- a/jinja/filters.py +++ b/jinja/filters.py @@ -11,6 +11,7 @@ from random import choice from urllib import urlencode, quote from jinja.utils import escape +from jinja.datastructure import Undefined try: @@ -40,16 +41,29 @@ def stringfilter(f): nargs[idx] = env.to_unicode(var) return f(env.to_unicode(value), *nargs) return wrapped + try: + decorator.__doc__ = f.__doc__ + decorator.__name__ = f.__name__ + except: + pass return decorator def do_replace(s, old, new, count=None): """ - {{ s|replace(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 - Return a copy of s with all occurrences of substring - old replaced by new. If the optional argument count is - given, only the first count occurrences are replaced. + {{ "Hello World"|replace("Hello", "Goodbye") }} + -> Goodbye World + + {{ "aaaaargh"|replace("a", "d'oh, ", 2) }} + -> d'oh, d'oh, aaargh """ if count is None: return s.replace(old, new) @@ -59,9 +73,7 @@ do_replace = stringfilter(do_replace) def do_upper(s): """ - {{ s|upper }} - - Return a copy of s converted to uppercase. + Convert a value to uppercase. """ return s.upper() do_upper = stringfilter(do_upper) @@ -69,9 +81,7 @@ do_upper = stringfilter(do_upper) def do_lower(s): """ - {{ s|lower }} - - Return a copy of s converted to lowercase. + Convert a value to lowercase. """ return s.lower() do_lower = stringfilter(do_lower) @@ -79,10 +89,12 @@ do_lower = stringfilter(do_lower) def do_escape(s, attribute=False): """ - {{ s|escape(attribute) }} + XML escape ``&``, ``<``, and ``>`` in a string of data. If the + optional parameter is `true` this filter will also convert + ``"`` to ``"``. This filter is just used if the environment + was configured with disabled `auto_escape`. - XML escape &, <, and > in a string of data. If attribute is - True it also converts ``"`` to ``"`` + This method will have no effect it the value is already escaped. """ return escape(s, attribute) do_escape = stringfilter(do_escape) @@ -90,9 +102,9 @@ do_escape = stringfilter(do_escape) def do_addslashes(s): """ - {{ s|addslashes }} - - Adds slashes to s. + Add backslashes in front of special characters to s. This method + might be useful if you try to fill javascript strings. Also have + a look at the `jsonencode` filter. """ return s.encode('utf-8').encode('string-escape').decode('utf-8') do_addslashes = stringfilter(do_addslashes) @@ -100,10 +112,8 @@ do_addslashes = stringfilter(do_addslashes) def do_capitalize(s): """ - {{ s|capitalize }} - - Return a copy of the string s with only its first character - capitalized. + Capitalize a value. The first character will be uppercase, all others + lowercase. """ return s.capitalize() do_capitalize = stringfilter(do_capitalize) @@ -111,33 +121,52 @@ do_capitalize = stringfilter(do_capitalize) def do_title(s): """ - {{ s|title }} - - Return a titlecased version of s, i.e. words start with uppercase - characters, all remaining cased characters have lowercase. + Return a titlecased version of the value. I.e. words will start with + uppercase letters, all remaining characters are lowercase. """ return s.title() do_title = stringfilter(do_title) -def do_default(default_value=u''): +def do_default(default_value=u'', boolean=False): """ - {{ s|default(default_value='') }} + 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') }} - In case of s isn't set or True default will return default_value - which is '' per default. + 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) }} """ - return lambda e, c, v: v or default_value + def wrapped(env, context, value): + if (boolean and not v) or v in (Undefined, None): + return default_value + return v + return wrapped do_default = stringfilter(do_default) def do_join(d=u''): """ - {{ sequence|join(d='') }} - Return a string which is the concatenation of the strings in the - sequence. The separator between elements is d which is an empty - string per default. + sequence. The separator between elements is an empty string per + default, you can define ith with the optional parameter: + + .. sourcecode:: jinja + + {{ [1, 2, 3]|join('|') }} + -> 1|2|3 + + {{ [1, 2, 3]|join }} + -> 123 """ def wrapped(env, context, value): d = env.to_unicode(d) @@ -147,12 +176,9 @@ def do_join(d=u''): def do_count(): """ - {{ var|count }} - - Return the length of var. In case if getting an integer or float + Return the length of the value. In case if getting an integer or float it will convert it into a string an return the length of the new - string. - If the object doesn't provide a __len__ function it will return zero + string. If the object has no length it will of corse return 0. """ def wrapped(env, context, value): try: @@ -164,29 +190,16 @@ def do_count(): return wrapped -def do_odd(): - """ - {{ var|odd }} - - Return true if the variable is odd. - """ - return lambda e, c, v: v % 2 == 1 - - -def do_even(): - """ - {{ var|even }} - - Return true of the variable is even. - """ - return lambda e, c, v: v % 2 == 0 - - def do_reversed(): """ - {{ var|reversed }} + Return a reversed list of the sequence filtered. You can use this + for example for reverse iteration: + + .. sourcecode:: jinja - Return a reversed list of the iterable filtered. + {% for item in seq|reversed %} + {{ item|e }} + {% endfor %} """ def wrapped(env, context, value): try: @@ -200,83 +213,59 @@ def do_reversed(): def do_center(value, width=80): """ - {{ var|center(80) }} - Centers the value in a field of a given width. """ return value.center(width) do_center = stringfilter(do_center) -def do_title(value): - """ - {{ var|title }} - - Capitalize the first character of all words. - """ - return value.title() -do_title = stringfilter(do_title) - - -def do_capitalize(value): - """ - {{ var|capitalize }} - - Capitalize the first character of the string. - """ - return value.capitalize() -do_capitalize = stringfilter(do_capitalize) - - def do_first(): """ - {{ var|first }} - - Return the frist item of a sequence or None. + Return the frist item of a sequence. """ def wrapped(env, context, seq): try: return iter(seq).next() except StopIteration: - return + return Undefined return wrapped def do_last(): """ - {{ var|last }} - Return the last item of a sequence. """ def wrapped(env, context, seq): try: return iter(_reversed(seq)).next() except (TypeError, StopIteration): - return + return Undefined return wrapped def do_random(): """ - {{ var|random }} - Return a random item from the sequence. """ def wrapped(env, context, seq): try: return choice(seq) except: - return + return Undefined return wrapped def do_urlencode(): """ - {{ var|urlencode }} + urlencode a string or directory. - {{ {'foo': 'bar'}|urlencode }} + .. sourcecode:: jinja - urlencode a string or directory. + {{ {'foo': 'bar', 'blub': 'blah'}|urlencode }} + -> foo=bar&blub=blah + + {{ 'Hello World' }} + -> Hello%20World """ def wrapped(env, context, value): if isinstance(value, dict): @@ -291,9 +280,12 @@ def do_urlencode(): def do_jsonencode(): """ - {{ var|jsonencode }} - JSON dump a variable. just works if simplejson is installed. + + .. sourcecode:: jinja + + {{ 'Hello World'|jsonencode }} + -> "Hello World" """ global simplejson try: @@ -315,8 +307,6 @@ FILTERS = { 'default': do_default, 'join': do_join, 'count': do_count, - 'odd': do_odd, - 'even': do_even, 'reversed': do_reversed, 'center': do_center, 'title': do_title, diff --git a/jinja/loaders.py b/jinja/loaders.py index a7a1221..98c4729 100644 --- a/jinja/loaders.py +++ b/jinja/loaders.py @@ -16,6 +16,9 @@ from jinja.translators.python import PythonTranslator from jinja.exceptions import TemplateNotFound +__all__ = ['FileSystemLoader'] + + def get_template_filename(searchpath, name): """ Return the filesystem filename wanted. @@ -68,10 +71,28 @@ class LoaderWrapper(object): class FileSystemLoader(object): """ - Loads templates from the filesystem:: + Loads templates from the filesystem: + + .. sourcecode:: python from jinja import Environment, FileSystemLoader e = Environment(loader=FileSystemLoader('templates/')) + + You can pass the following keyword arguments to the loader on + initialisation: + + =================== ================================================= + ``searchpath`` String with the path to the templates on the + filesystem. + ``use_cache`` Set this to ``True`` to enable memory caching. + This is usually a good idea in production mode, + but disable it during development since it won't + reload template changes automatically. + This only works in persistent environments like + FastCGI. + ``cache_size`` Number of template instance you want to cache. + Defaults to ``40``. + =================== ================================================= """ def __init__(self, searchpath, use_cache=False, cache_size=40): diff --git a/jinja/nodes.py b/jinja/nodes.py index 256bcbd..400e4e2 100644 --- a/jinja/nodes.py +++ b/jinja/nodes.py @@ -133,9 +133,9 @@ class IfCondition(Node): def get_items(self): result = [] - for test in tests: + for test in self.tests: result.extend(test) - result.append(self._else) + result.append(self.else_) return result def __repr__(self): diff --git a/jinja/tests.py b/jinja/tests.py index a8692eb..95220e5 100644 --- a/jinja/tests.py +++ b/jinja/tests.py @@ -19,54 +19,52 @@ regex_type = type(number_re) def test_odd(): """ - {{ var is odd }} - - Return True if the variable is odd. + Return true if the variable is odd. """ return lambda e, c, v: v % 2 == 1 def test_even(): """ - {{ var is even }} - - Return True of the variable is even. + Return true of the variable is even. """ return lambda e, c, v: v % 2 == 0 def test_defined(): """ - {{ var is defined }} + Return true if the variable is defined: + + .. sourcecode:: jinja - Return True if the variable is defined. + {% if variable is defined %} + value of variable: {{ variable }} + {% else %} + variable is not defined + {% endif %} + + See also the ``default`` filter. """ return lambda e, c, v: v is not Undefined def test_lower(): """ - {{ var is lower }} - - Return True if the variable is lowercase. + Return true if the variable is lowercase. """ return lambda e, c, v: isinstance(v, basestring) and v.islower() def test_upper(): """ - {{ var is upper }} - - Return True if the variable is uppercase. + Return true if the variable is uppercase. """ return lambda e, c, v: isinstance(v, basestring) and v.isupper() def test_numeric(): """ - {{ var is numeric }} - - Return True if the variable is numeric. + Return true if the variable is numeric. """ return lambda e, c, v: isinstance(v, (int, long, float)) or ( isinstance(v, basestring) and @@ -75,9 +73,8 @@ def test_numeric(): def test_sequence(): """ - {{ var is sequence }} - - Return True if the variable is a sequence. + Return true if the variable is a sequence. Sequences are variables + that are iterable. """ def wrapped(environment, context, value): try: @@ -91,12 +88,18 @@ def test_sequence(): def test_matching(regex): """ - {{ var is matching('\d+$') }} - Test if the variable matches the regular expression given. If the regular expression is a string additional slashes are automatically added, if it's a compiled regex - it's used without any modifications. + it's used without any modifications: + + .. sourcecode:: jinja + + {% if var is matching('\d+$') %} + var looks like a number + {% else %} + var doesn't really look like a number + {% endif %} """ if isinstance(regex, unicode): regex = re.compile(regex.encode('unicode-escape'), re.U) diff --git a/jinja/translators/python.py b/jinja/translators/python.py index 31c4be7..b410b59 100644 --- a/jinja/translators/python.py +++ b/jinja/translators/python.py @@ -674,7 +674,7 @@ class PythonTranslator(Translator): handle foo or bar. """ return ' or '.join([ - self.handle_node(n) for n in self.nodse + self.handle_node(n) for n in node.nodes ]) def handle_not(self, node): diff --git a/jinja/utils.py b/jinja/utils.py index de3f570..1b0df50 100644 --- a/jinja/utils.py +++ b/jinja/utils.py @@ -9,7 +9,7 @@ :license: BSD, see LICENSE for more details. """ import re -from jinja.datastructure import safe_types +from jinja.datastructure import safe_types, Markup _escape_pairs = { @@ -30,5 +30,5 @@ def escape(x, attribute=False): """ if type(x) in safe_types: return x - return _escape_res[not attribute].sub(lambda m: _escape_pairs[m.group()], - unicode(x)) + return Markup(_escape_res[not attribute].sub(lambda m: + _escape_pairs[m.group()], unicode(x))) -- 2.26.2
    + {% if file_id == 'index' %} + +

    {{ title }}

    + {% else %} +

    Jinja

    +

    {{ title }}

    + {% endif %} + {% if file_id != 'index' or toc %} +
    +

    Navigation

    +
    + {% if toc %} +

    Contents

    +
      + {% for key, value in toc %} +
    • {{ value }}
    • + {% endfor %} +
    + {% endif %} +
    + {% endif %} +
    + {{ body }} +
    +