From: Armin Ronacher Date: Fri, 2 Mar 2007 19:42:18 +0000 (+0100) Subject: [svn] add first part of jinja documentation X-Git-Tag: 2.0rc1~476 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=37a8851c69709031491d32a8c71468a6c3b65453;p=jinja2.git [svn] add first part of jinja documentation --HG-- branch : trunk --- 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 0000000..c672118 Binary files /dev/null and b/docs/build/jinjabanner.png differ diff --git a/docs/build/jinjalogo.png b/docs/build/jinjalogo.png new file mode 100644 index 0000000..17d6dc3 Binary files /dev/null and b/docs/build/jinjalogo.png differ diff --git a/docs/build/style.css b/docs/build/style.css new file mode 100644 index 0000000..64cba24 --- /dev/null +++ b/docs/build/style.css @@ -0,0 +1,196 @@ +body { + background-color: #333; + margin: 0; + padding: 0; + font-family: 'Georgia', serif; + font-size: 15px; + color: #111; +} + +#content { + background-color: white; + background-image: url(watermark.png); + padding: 10px; + margin: 25px; + border: 4px solid #ddd; +} + +h1 { + margin: 0; + padding: 0; + height: 80px; + background-image: url(jinjabanner.png); + background-repeat: no-repeat; +} + +h1 span { + display: none; +} + +h2.subheading { + margin: -55px 0 35px 200px; + font-weight: normal; + font-size: 30px; + color: #444; +} + +h2.plain { + margin: 0; +} + +#jinjalogo { + background-image: url(jinjalogo.png); + background-repeat: no-repeat; + width: 400px; + height: 160px; +} + +#contentwrapper { + max-width: 700px; + padding: 0 0 20px 18px; +} + +#contentwrapper h3, +#contentwrapper h3 a { + color: #b41717; + font-size: 26px; + margin: 20px 0 0 -5px; +} + +#contentwrapper h4, +#contentwrapper h4 a { + color: #b41717; + font-size: 20px; + margin: 20px 0 0 0; +} + +table.docutils { + border-collapse: collapse; + border: 2px solid #aaa; + margin: 0.5em 1.5em 0.5em 1.5em; +} + +table.docutils td { + padding: 2px; + border: 1px solid #ddd; +} + +p, li, dd, dt, blockquote { + color: #333; +} + +p { + line-height: 150%; + margin-bottom: 0; + margin-top: 10px; + text-align: justify; +} + +hr { + border-top: 1px solid #ccc; + border-bottom: 0; + border-right: 0; + border-left: 0; + margin-bottom: 10px; + margin-top: 20px; +} + +dl { + margin-left: 10px; +} + +li, dt { + margin-top: 5px; +} + +dt { + font-weight: bold; +} + +th { + text-align: left; + padding: 3px; + background-color: #f2f2f2; +} + +a { + color: #b41717; +} + +a:hover { + color: #444; +} + +pre { + background-color: #f9f9f9; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + padding: 5px; + font-size: 13px; + font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; +} + +tt { + font-size: 13px; + font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; + color: black; + padding: 1px 2px 1px 2px; + background-color: #f0f0f0; +} + +cite { + /* abusing , 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 0000000..297d899 Binary files /dev/null and b/docs/build/watermark.png differ 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 + + + + + +
+ {% 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 }} +
+
+ + +\ +''') + +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

+ + +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)))