Permalink
Browse files

more updates on the extension API

--HG--
branch : trunk
  • Loading branch information...
1 parent 023b5e9 commit 762079cd25921596b9e1227e6c3bad6d465c430f @mitsuhiko mitsuhiko committed May 8, 2008
Showing with 132 additions and 115 deletions.
  1. BIN docs/_static/metal.png
  2. +3 −2 docs/_static/style.css
  3. +1 −1 docs/api.rst
  4. +18 −21 docs/cache_extension.py
  5. +52 −52 docs/extensions.rst
  6. +9 −0 jinja2/environment.py
  7. +38 −36 jinja2/ext.py
  8. +2 −1 jinja2/nodes.py
  9. +9 −2 jinja2/parser.py
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
@@ -179,7 +179,7 @@ a:hover {
}
pre {
- background-color: #f9f9f9;
+ background: #ededed url(metal.png);
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
padding: 5px;
@@ -296,7 +296,8 @@ table.indextable dl dd a {
dl.function dt,
dl.class dt,
-dl.exception dt {
+dl.exception dt,
+dl.method dt {
font-weight: normal;
}
View
@@ -48,7 +48,7 @@ High Level API
--------------
.. autoclass:: jinja2.environment.Environment([options])
- :members: from_string, get_template, join_path, parse, lex
+ :members: from_string, get_template, join_path, parse, lex, extend
.. attribute:: shared
View
@@ -2,19 +2,18 @@
from jinja2.ext import Extension
-class CacheExtension(Extension):
- """Adds support for fragment caching to Jinja2."""
+class FragmentCacheExtension(Extension):
+ # a set of names that trigger the extension.
tags = set(['cache'])
def __init__(self, environment):
- Extension.__init__(self, environment)
+ super(FragmentCacheExtension, self).__init__(environment)
- # default dummy implementations. If the class does not implement
- # those methods we add some noop defaults.
- if not hasattr(environment, 'add_fragment_to_cache'):
- environment.add_fragment_to_cache = lambda n, v, t: None
- if not hasattr(environment, 'load_fragment_from_cache'):
- environment.load_fragment_from_cache = lambda n: None
+ # add the defaults to the environment
+ environment.extend(
+ fragment_cache_prefix='',
+ fragment_cache=None
+ )
def parse(self, parser):
# the first token is the token that started the tag. In our case
@@ -26,13 +25,10 @@ def parse(self, parser):
# now we parse a single expression that is used as cache key.
args = [parser.parse_expression()]
- # if there is a comma, someone provided the timeout. parse the
- # timeout then
- if parser.stream.current.type is 'comma':
- parser.stream.next()
+ # if there is a comma, the user provided a timeout. If not use
+ # None as second parameter.
+ if parser.skip_comma():
args.append(parser.parse_expression())
-
- # otherwise set the timeout to `None`
else:
args.append(nodes.Const(None))
@@ -49,13 +45,14 @@ def parse(self, parser):
def _cache_support(self, name, timeout, caller):
"""Helper callback."""
- # try to load the block from the cache
- rv = self.environment.load_fragment_from_cache(name)
- if rv is not None:
- return rv
+ key = self.environment.fragment_cache_prefix + name
+ # try to load the block from the cache
# if there is no fragment in the cache, render it and store
# it in the cache.
- rv = caller()
- self.environment.add_fragment_to_cache(name, rv, timeout)
+ rv = self.environment.fragment_cache.get(key)
+ if rv is None:
+ return rv
+ rv = caller()
+ self.environment.fragment_cache.add(key, rv, timeout)
return rv
View
@@ -3,8 +3,6 @@
Extensions
==========
-.. module:: jinja2.ext
-
Jinja2 supports extensions that can add extra filters, tests, globals or even
extend the parser. The main motivation of extensions is it to move often used
code into a reusable class like adding support for internationalization.
@@ -32,9 +30,46 @@ used in combination with `gettext`_ or `babel`_. If the i18n extension is
enabled Jinja2 provides a `trans` statement that marks the wrapped string as
translatable and calls `gettext`.
-After enabling dummy `_`, `gettext` and `ngettext` functions are added to
-the template globals. A internationalized application has to override those
-methods with more useful versions.
+After enabling dummy `_` function that forwards calls to `gettext` is added
+to the environment globals. An internationalized application then has to
+provide at least an `gettext` and optoinally a `ngettext` function into the
+namespace. Either globally or for each rendering.
+
+After enabling of the extension the environment provides the following
+additional methods:
+
+.. method:: jinja2.Environment.install_gettext_translations(translations)
+
+ Installs a translation globally for that environment. The tranlations
+ object provided must implement at least `ugettext` and `ungettext`.
+ The `gettext.NullTranslations` and `gettext.GNUTranslations` classes
+ as well as `Babel`_\s `Translations` class are supported.
+
+.. method:: jinja2.Environment.install_null_translations()
+
+ Install dummy gettext functions. This is useful if you want to prepare
+ the application for internationalization but don't want to implement the
+ full internationalization system yet.
+
+.. method:: jinja2.Environment.uninstall_gettext_translations()
+
+ Uninstall the translations again.
+
+.. method:: jinja2.Environment.extract_translations(source)
+
+ Extract localizable strings from the given template node or source.
+
+ For every string found this function yields a ``(lineno, function,
+ message)`` tuple, where:
+
+ * `lineno` is the number of the line on which the string was found,
+ * `function` is the name of the `gettext` function used (if the
+ string was extracted from embedded Python code), and
+ * `message` is the string itself (a `unicode` object, or a tuple
+ of `unicode` objects for functions with multiple string arguments).
+
+ If `Babel`_ is installed :ref:`the babel integration <babel-integration>`
+ can be used to extract strings for babel.
For a web application that is available in multiple languages but gives all
the users the same language (for example a multilingual forum software
@@ -43,47 +78,25 @@ translation methods to the environment at environment generation time::
translations = get_gettext_translations()
env = Environment(extensions=['jinja.ext.i18n'])
- env.globals.update(
- gettext=translations.ugettext,
- ngettext=translations.ungettext
- )
+ env.install_gettext_translations(translations)
The `get_gettext_translations` function would return the translator for the
-current configuration. Keep in mind that Jinja2 uses unicode internally so
-you must pass the `ugettext` and `ungettext` functions to the template.
-
-The default `_` function injected by the extension calls `gettext`
-automatically.
-
-If you want to pass the gettext function into the context at render time
-because you don't know the language/translations earlier and the optimizer
-is enabled (which it is per default), you have to unregister the `gettext`
-and `ugettext` functions first::
-
- del env.globals['gettext'], env.globals['ugettext']
-
-Jinja2 also provides a way to extract recognized strings. For one the
-`jinja.ext` module provides a function that can return all the occurences
-of gettext calls in a node (as returned by :meth:`Environment.parse`):
-
-.. autofunction:: extract_from_ast
-
-If `babel`_ is installed :ref:`the babel integration <babel-integration>`
-can be used to.
+current configuration. (For example by using `gettext.find`)
The usage of the `i18n` extension for template designers is covered as part
:ref:`of the template documentation <i18n-in-templates>`.
-
.. _gettext: http://docs.python.org/dev/library/gettext
-.. _babel: http://babel.edgewall.org/
+.. _Babel: http://babel.edgewall.org/
.. _writing-extensions:
Writing Extensions
------------------
+.. module:: jinja2.ext
+
By writing extensions you can add custom tags to Jinja2. This is a non trival
task and usually not needed as the default tags and expressions cover all
common use cases. The i18n extension is a good example of why extensions are
@@ -92,32 +105,19 @@ useful, another one would be fragment caching.
Example Extension
~~~~~~~~~~~~~~~~~
-The following example implements a `cache` tag for Jinja2:
+The following example implements a `cache` tag for Jinja2 by using the
+`Werkzeug`_ caching contrib module:
.. literalinclude:: cache_extension.py
:language: python
-In order to use the cache extension it makes sense to subclass the environment
-to implement the `add_fragment_to_cache` and `load_fragment_from_cache`
-methods. The following example shows how to use the `Werkzeug`_ caching
-with the extension from above::
+And here is how you use it in an environment::
from jinja2 import Environment
from werkzeug.contrib.cache import SimpleCache
- cache = SimpleCache()
- cache_prefix = 'tempalte_fragment/'
-
- class MyEnvironment(Environment):
-
- def __init__(self):
- Environment.__init__(self, extensions=[CacheExtension])
-
- def add_fragment_to_cache(self, key, value, timeout):
- cache.add(cache_prefix + key, value, timeout)
-
- def load_fragment_from_cache(self, key):
- return cache.get(cache_prefix + key)
+ env = Environment(extensions=[FragmentCacheExtension])
+ env.fragment_cache = SimpleCache()
.. _Werkzeug: http://werkzeug.pocoo.org/
@@ -147,8 +147,8 @@ expressions of different types. The following methods may be used by
extensions:
.. autoclass:: jinja2.parser.Parser
- :members: parse_expression, parse_tuple, parse_statements, ignore_colon,
- free_identifier
+ :members: parse_expression, parse_tuple, parse_statements, skip_colon,
+ skip_comma, free_identifier
.. attribute:: filename
View
@@ -222,6 +222,15 @@ def __init__(self,
_environment_sanity_check(self)
+ def extend(self, **attributes):
+ """Add the items to the instance of the environment if they do not exist
+ yet. This is used by :ref:`extensions <writing-extensions>` to register
+ callbacks and configuration values without breaking inheritance.
+ """
+ for key, value in attributes.iteritems():
+ if not hasattr(self, key):
+ setattr(self, key, value)
+
def overlay(self, block_start_string=missing, block_end_string=missing,
variable_start_string=missing, variable_end_string=missing,
comment_start_string=missing, comment_end_string=missing,
View
@@ -40,6 +40,17 @@ class Extension(object):
may not store environment specific data on `self`. The reason for this is
that an extension can be bound to another environment (for overlays) by
creating a copy and reassigning the `environment` attribute.
+
+ As extensions are created by the environment they cannot accept any
+ arguments for configuration. One may want to work around that by using
+ a factory function, but that is not possible as extensions are identified
+ by their import name. The correct way to configure the extension is
+ storing the configuration values on the environment. Because this way the
+ environment ends up acting as central configuration storage the
+ attributes may clash which is why extensions have to ensure that the names
+ they choose for configuration are not too generic. ``prefix`` for example
+ is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
+ name as includes the name of the extension (fragment cache).
"""
__metaclass__ = ExtensionRegistry
@@ -74,49 +85,40 @@ def attr(self, name, lineno=None):
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
-class CacheExtension(Extension):
- """An example extension that adds cacheable blocks."""
- tags = set(['cache'])
+class InternationalizationExtension(Extension):
+ """This extension adds gettext support to Jinja2."""
+ tags = set(['trans'])
def __init__(self, environment):
Extension.__init__(self, environment)
- environment.globals['__cache_ext_support'] = self.cache_support
-
- def cache_support(self, name, timeout, caller):
- """Helper for the cache_fragment function."""
- if not hasattr(environment, 'cache_support'):
- return caller()
- args = [name]
- if timeout is not None:
- args.append(timeout)
- return self.environment.cache_support(generate=caller, *args)
+ environment.globals['_'] = contextfunction(lambda c, x: c['gettext'](x))
+ environment.extend(
+ install_gettext_translations=self._install,
+ install_null_translations=self._install_null,
+ uninstall_gettext_translations=self._uninstall,
+ extract_translations=self._extract
+ )
- def parse(self, parser):
- lineno = parser.stream.next().lineno
- args = [parser.parse_expression()]
- if parser.stream.current.type is 'comma':
- parser.stream.next()
- args.append(parser.parse_expression())
- else:
- args.append(nodes.Const(None, lineno=lineno))
- body = parser.parse_statements(('name:endcache',), drop_needle=True)
- return nodes.CallBlock(
- nodes.Call(nodes.Name('__cache_ext_support', 'load', lineno=lineno),
- args, [], None, None), [], [], body, lineno=lineno
+ def _install(self, translations):
+ self.environment.globals.update(
+ gettext=translations.ugettext,
+ ngettext=translations.ungettext
)
+ def _install_null(self):
+ self.environment.globals.update(
+ gettext=lambda x: x,
+ ngettext=lambda s, p, n: (n != 1 and (p,) or (s,))[0]
+ )
-class InternationalizationExtension(Extension):
- """This extension adds gettext support to Jinja."""
- tags = set(['trans'])
+ def _uninstall(self, translations):
+ for key in 'gettext', 'ngettext':
+ self.environment.globals.pop(key, None)
- def __init__(self, environment):
- Extension.__init__(self, environment)
- environment.globals.update({
- '_': contextfunction(lambda c, x: c['gettext'](x)),
- 'gettext': lambda x: x,
- 'ngettext': lambda s, p, n: (s, p)[n != 1]
- })
+ def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
+ if isinstance(source, basestring):
+ source = self.environment.parse(source)
+ return extract_from_ast(source, gettext_functions)
def parse(self, parser):
"""Parse a translatable tag."""
@@ -132,7 +134,7 @@ def parse(self, parser):
parser.stream.expect('comma')
# skip colon for python compatibility
- if parser.ignore_colon():
+ if parser.skip_colon():
break
name = parser.stream.expect('name')
View
@@ -742,7 +742,8 @@ class ImportedName(Expr):
class InternalName(Expr):
"""An internal name in the compiler. You cannot create these nodes
- yourself but the parser provides a `free_identifier` method that creates
+ yourself but the parser provides a
+ :meth:`~jinja2.parser.Parser.free_identifier` method that creates
a new identifier for you. This identifier is not available from the
template and is not threated specially by the compiler.
"""
Oops, something went wrong.

0 comments on commit 762079c

Please sign in to comment.