some documentation improvements, jinja escapes " and ' now, both into charpoints...
[jinja2.git] / jinja2 / environment.py
index a982e8e8234a26a284b2c5e98b3564e2e978e455..35bbb15fe6daf1d68ad4c12bbab56bc47f16201e 100644 (file)
@@ -5,18 +5,19 @@
 
     Provides a class that holds runtime and parsing time options.
 
-    :copyright: 2007 by Armin Ronacher.
+    :copyright: 2008 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
 import sys
+from jinja2.defaults import *
 from jinja2.lexer import Lexer
 from jinja2.parser import Parser
 from jinja2.optimizer import optimize
 from jinja2.compiler import generate
-from jinja2.runtime import Undefined, TemplateContext
-from jinja2.debug import translate_exception
-from jinja2.utils import import_string, LRUCache
-from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
+from jinja2.runtime import Undefined, Context
+from jinja2.debug import translate_exception, translate_syntax_error
+from jinja2.exceptions import TemplateSyntaxError
+from jinja2.utils import import_string, LRUCache, Markup, missing, concat
 
 
 # for direct template usage we have up to ten living environments
@@ -35,40 +36,113 @@ def get_spontaneous_environment(*args):
     if env is not None:
         return env
     _spontaneous_environments[args] = env = Environment(*args)
+    env.shared = True
     return env
 
 
-def template_from_code(environment, code, globals, uptodate=None,
-                       template_class=None):
-    """Generate a new template object from code.  It's used in the
-    template constructor and the loader `load` implementation.
+def create_cache(size):
+    """Return the cache class for the given size."""
+    if size == 0:
+        return None
+    if size < 0:
+        return {}
+    return LRUCache(size)
+
+
+def load_extensions(environment, extensions):
+    """Load the extensions from the list and bind it to the environment.
+    Returns a new list of instanciated environments.
     """
-    t = object.__new__(template_class or environment.template_class)
-    namespace = {
-        'environment':          environment,
-        '__jinja_template__':   t
-    }
-    exec code in namespace
-    t.environment = environment
-    t.name = namespace['name']
-    t.filename = code.co_filename
-    t.root_render_func = namespace['root']
-    t.blocks = namespace['blocks']
-    t.globals = globals
-
-    # debug and loader helpers
-    t._debug_info = namespace['debug_info']
-    t._uptodate = uptodate
-
-    return t
+    result = []
+    for extension in extensions:
+        if isinstance(extension, basestring):
+            extension = import_string(extension)
+        result.append(extension(environment))
+    return result
 
 
-class Environment(object):
-    """The Jinja environment.
+def _environment_sanity_check(environment):
+    """Perform a sanity check on the environment."""
+    assert issubclass(environment.undefined, Undefined), 'undefined must ' \
+           'be a subclass of undefined because filters depend on it.'
+    assert environment.block_start_string != \
+           environment.variable_start_string != \
+           environment.comment_start_string, 'block, variable and comment ' \
+           'start strings must be different'
+    return environment
 
-    The core component of Jinja is the `Environment`. It contains
+
+class Environment(object):
+    """The core component of Jinja is the `Environment`.  It contains
     important shared variables like configuration, filters, tests,
-    globals and others.
+    globals and others.  Instances of this class may be modified if
+    they are not shared and if no template was loaded so far.
+    Modifications on environments after the first template was loaded
+    will lead to surprising effects and undefined behavior.
+
+    Here the possible initialization parameters:
+
+    `block_start_string`
+        The string marking the begin of a block.  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 ``'{{'``.
+
+    `variable_stop_string`
+        The string marking the end 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 ``'#}'``.
+
+    `line_statement_prefix`
+        If given and a string, this will be used as prefix for line based
+        statements.  See also :ref:`line-statements`.
+
+    `trim_blocks`
+        If this is set to ``True`` the first newline after a block is
+        removed (block, not variable tag!).  Defaults to `False`.
+
+    `extensions`
+        List of Jinja extensions to use.  This can either be import paths
+        as strings or extension classes.
+
+    `optimized`
+        should the optimizer be enabled?  Default is `True`.
+
+    `undefined`
+        :class:`Undefined` or a subclass of it that is used to represent
+        undefined values in the template.
+
+    `finalize`
+        A callable that finalizes the variable.  Per default no finalizing
+        is applied.
+
+    `autoescape`
+        If set to true the XML/HTML autoescaping feature is enabled.
+
+    `loader`
+        The template loader for this environment.
+
+    `cache_size`
+        The size of the cache.  Per default this is ``50`` which means that if
+        more than 50 templates are loaded the loader will clean out the least
+        recently used template.  If the cache size is set to ``0`` templates are
+        recompiled all the time, if the cache size is ``-1`` the cache will not
+        be cleaned.
+
+    `auto_reload`
+        Some loaders load templates from locations where the template sources
+        may change (ie: file system or database).  If `auto_reload` is set to
+        `True` (default) every time a template is requested the loader checks
+        if the source changed and if yes, it will reload the template.  For
+        higher performance it's possible to disable that.
     """
 
     #: if this environment is sandboxed.  Modifying this variable won't make
@@ -76,20 +150,33 @@ class Environment(object):
     #: have a look at jinja2.sandbox
     sandboxed = False
 
+    #: True if the environment is just an overlay
+    overlay = False
+
+    #: the environment this environment is linked to if it is an overlay
+    linked_to = None
+
+    #: shared environments have this set to `True`.  A shared environment
+    #: must not be modified
+    shared = False
+
     def __init__(self,
-                 block_start_string='{%',
-                 block_end_string='%}',
-                 variable_start_string='{{',
-                 variable_end_string='}}',
-                 comment_start_string='{#',
-                 comment_end_string='#}',
-                 line_statement_prefix=None,
+                 block_start_string=BLOCK_START_STRING,
+                 block_end_string=BLOCK_END_STRING,
+                 variable_start_string=VARIABLE_START_STRING,
+                 variable_end_string=VARIABLE_END_STRING,
+                 comment_start_string=COMMENT_START_STRING,
+                 comment_end_string=COMMENT_END_STRING,
+                 line_statement_prefix=LINE_STATEMENT_PREFIX,
                  trim_blocks=False,
                  extensions=(),
                  optimized=True,
                  undefined=Undefined,
-                 finalize=unicode,
-                 loader=None):
+                 finalize=None,
+                 autoescape=False,
+                 loader=None,
+                 cache_size=50,
+                 auto_reload=True):
         # !!Important notice!!
         #   The constructor accepts quite a few arguments that should be
         #   passed by keyword rather than position.  However it's important to
@@ -99,44 +186,7 @@ class Environment(object):
         #       -   unittests
         #   If parameter changes are required only add parameters at the end
         #   and don't change the arguments (or the defaults!) of the arguments
-        #   up to (but excluding) loader.
-        """Here the possible initialization 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 ``'#}'``.
-        `line_statement_prefix`   If given and a string, this will be used as
-                                  prefix for line based statements.  See the
-                                  documentation for more details.
-        `trim_blocks`             If this is set to ``True`` the first newline
-                                  after a block is removed (block, not
-                                  variable tag!). Defaults to ``False``.
-        `extensions`              List of Jinja extensions to use.
-        `optimized`               should the optimizer be enabled?  Default is
-                                  ``True``.
-        `undefined`               a subclass of `Undefined` that is used to
-                                  represent undefined variables.
-        `finalize`                A callable that finalizes the variable.  Per
-                                  default this is `unicode`, other useful
-                                  builtin finalizers are `escape`.
-        `loader`                  the loader which should be used.
-        ========================= ============================================
-        """
-
-        # santity checks
-        assert issubclass(undefined, Undefined), 'undefined must be ' \
-               'a subclass of undefined because filters depend on it.'
-        assert block_start_string != variable_start_string != \
-               comment_start_string, 'block, variable and comment ' \
-               'start strings must be different'
+        #   existing already.
 
         # lexer / parser information
         self.block_start_string = block_start_string
@@ -152,6 +202,7 @@ class Environment(object):
         self.undefined = undefined
         self.optimized = optimized
         self.finalize = finalize
+        self.autoescape = autoescape
 
         # defaults
         self.filters = DEFAULT_FILTERS.copy()
@@ -160,16 +211,59 @@ class Environment(object):
 
         # set the loader provided
         self.loader = loader
-
-        # create lexer
-        self.lexer = Lexer(self)
+        self.cache = create_cache(cache_size)
+        self.auto_reload = auto_reload
 
         # load extensions
-        self.extensions = []
-        for extension in extensions:
-            if isinstance(extension, basestring):
-                extension = import_string(extension)
-            self.extensions.append(extension(self))
+        self.extensions = load_extensions(self, extensions)
+
+        _environment_sanity_check(self)
+
+    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,
+                line_statement_prefix=missing, trim_blocks=missing,
+                extensions=missing, optimized=missing, undefined=missing,
+                finalize=missing, autoescape=missing, loader=missing,
+                cache_size=missing, auto_reload=missing):
+        """Create a new overlay environment that shares all the data with the
+        current environment except of cache and the overriden attributes.
+        Extensions cannot be removed for a overlayed environment.  A overlayed
+        environment automatically gets all the extensions of the environment it
+        is linked to plus optional extra extensions.
+
+        Creating overlays should happen after the initial environment was set
+        up completely.  Not all attributes are truly linked, some are just
+        copied over so modifications on the original environment may not shine
+        through.
+        """
+        args = dict(locals())
+        del args['self'], args['cache_size'], args['extensions']
+
+        rv = object.__new__(self.__class__)
+        rv.__dict__.update(self.__dict__)
+        rv.overlay = True
+        rv.linked_to = self
+
+        for key, value in args.iteritems():
+            if value is not missing:
+                setattr(rv, key, value)
+
+        if cache_size is not missing:
+            rv.cache = create_cache(cache_size)
+
+        rv.extensions = []
+        for extension in self.extensions:
+            rv.extensions.append(extension.bind(self))
+        if extensions is not missing:
+            rv.extensions.extend(load_extensions(extensions))
+
+        return _environment_sanity_check(rv)
+
+    @property
+    def lexer(self):
+        """Return a fresh lexer for the environment."""
+        return Lexer(self)
 
     def subscribe(self, obj, argument):
         """Get an item or attribute of an object."""
@@ -181,33 +275,43 @@ class Environment(object):
             except (TypeError, LookupError):
                 return self.undefined(obj=obj, name=argument)
 
-    def parse(self, source, name=None):
-        """Parse the sourcecode and return the abstract syntax tree. This tree
-        of nodes is used by the compiler to convert the template into
-        executable source- or bytecode.
+    def parse(self, source, filename=None):
+        """Parse the sourcecode and return the abstract syntax tree.  This
+        tree of nodes is used by the compiler to convert the template into
+        executable source- or bytecode.  This is useful for debugging or to
+        extract information from templates.
         """
-        return Parser(self, source, name).parse()
+        try:
+            return Parser(self, source, filename).parse()
+        except TemplateSyntaxError, e:
+            exc_type, exc_value, tb = translate_syntax_error(e)
+            raise exc_type, exc_value, tb
 
     def lex(self, source, name=None):
-        """Lex the given sourcecode and return a generator that yields tokens.
-        The stream returned is not usable for Jinja but can be used if
-        Jinja templates should be processed by other tools (for example
-        syntax highlighting etc)
-
-        The tuples are returned in the form ``(lineno, token, value)``.
+        """Lex the given sourcecode and return a generator that yields
+        tokens as tuples in the form ``(lineno, token_type, value)``.
         """
         return self.lexer.tokeniter(source, name)
 
     def compile(self, source, name=None, filename=None, globals=None,
                 raw=False):
-        """Compile a node or source.  The name is the load name of the
-        template after it was joined using `join_path` if necessary,
-        filename is the estimated filename of the template on the file
-        system.  If the template came from a database or memory this
-        can be omitted.
+        """Compile a node or template source code.  The `name` parameter is
+        the load name of the template after it was joined using
+        :meth:`join_path` if necessary, not the filename on the file system.
+        the `filename` parameter is the estimated filename of the template on
+        the file system.  If the template came from a database or memory this
+        can be omitted.  The `globals` parameter can be used to provide extra
+        variables at compile time for the template.  In the future the
+        optimizer will be able to evaluate parts of the template at compile
+        time based on those variables.
+
+        The return value of this method is a python code object.  If the `raw`
+        parameter is `True` the return value will be a string with python
+        code equivalent to the bytecode returned otherwise.  This method is
+        mainly used internally.
         """
         if isinstance(source, basestring):
-            source = self.parse(source, name)
+            source = self.parse(source, filename)
         if self.optimized:
             node = optimize(source, self, globals or {})
         source = generate(node, self, name, filename)
@@ -221,24 +325,53 @@ class Environment(object):
 
     def join_path(self, template, parent):
         """Join a template with the parent.  By default all the lookups are
-        relative to the loader root, but if the paths should be relative this
-        function can be used to calculate the real filename."""
+        relative to the loader root so this method returns the `template`
+        parameter unchanged, but if the paths should be relative to the
+        parent template, this function can be used to calculate the real
+        template name.
+
+        Subclasses may override this method and implement template path
+        joining here.
+        """
         return template
 
     def get_template(self, name, parent=None, globals=None):
-        """Load a template."""
+        """Load a template from the loader.  If a loader is configured this
+        method ask the loader for the template and returns a :class:`Template`.
+        If the `parent` parameter is not `None`, :meth:`join_path` is called
+        to get the real template name before loading.
+
+        The `globals` parameter can be used to provide compile-time globals.
+        In the future this will allow the optimizer to render parts of the
+        templates at compile-time.
+
+        If the template does not exist a :exc:`TemplateNotFound` exception is
+        raised.
+        """
         if self.loader is None:
             raise TypeError('no loader for this environment specified')
         if parent is not None:
             name = self.join_path(name, parent)
-        globals = self.make_globals(globals)
-        return self.loader.load(self, name, globals)
+
+        if self.cache is not None:
+            template = self.cache.get(name)
+            if template is not None and (not self.auto_reload or \
+                                         template.is_up_to_date):
+                return template
+
+        template = self.loader.load(self, name, self.make_globals(globals))
+        if self.cache is not None:
+            self.cache[name] = template
+        return template
 
     def from_string(self, source, globals=None, template_class=None):
-        """Load a template from a string."""
+        """Load a template from a string.  This parses the source given and
+        returns a :class:`Template` object.
+        """
         globals = self.make_globals(globals)
-        return template_from_code(self, self.compile(source, globals=globals),
-                                  globals, template_class)
+        cls = template_class or self.template_class
+        return cls.from_code(self, self.compile(source, globals=globals),
+                             globals, None)
 
     def make_globals(self, d):
         """Return a dict for the globals."""
@@ -251,8 +384,8 @@ class Template(object):
     """The central template object.  This class represents a compiled template
     and is used to evaluate it.
 
-    Normally the template object is generated from an `Environment` but it
-    also has a constructor that makes it possible to create a template
+    Normally the template object is generated from an :class:`Environment` but
+    it also has a constructor that makes it possible to create a template
     instance directly using the constructor.  It takes the same arguments as
     the environment constructor but it's not possible to specify a loader.
 
@@ -290,34 +423,77 @@ class Template(object):
                 extensions=(),
                 optimized=True,
                 undefined=Undefined,
-                finalize=unicode):
+                finalize=None,
+                autoescape=False):
         env = get_spontaneous_environment(
             block_start_string, block_end_string, variable_start_string,
             variable_end_string, comment_start_string, comment_end_string,
             line_statement_prefix, trim_blocks, tuple(extensions), optimized,
-            undefined, finalize)
+            undefined, finalize, autoescape, None, 0, False)
         return env.from_string(source, template_class=cls)
 
+    @classmethod
+    def from_code(cls, environment, code, globals, uptodate=None):
+        """Creates a template object from compiled code and the globals.  This
+        is used by the loaders and environment to create a template object.
+        """
+        t = object.__new__(cls)
+        namespace = {
+            'environment':          environment,
+            '__jinja_template__':   t
+        }
+        exec code in namespace
+        t.environment = environment
+        t.name = namespace['name']
+        t.filename = code.co_filename
+        t.root_render_func = namespace['root']
+        t.blocks = namespace['blocks']
+        t.globals = globals
+
+        # debug and loader helpers
+        t._debug_info = namespace['debug_info']
+        t._uptodate = uptodate
+
+        return t
+
     def render(self, *args, **kwargs):
-        """Render the template into a string."""
+        """This method accepts the same arguments as the `dict` constructor:
+        A dict, a dict subclass or some keyword arguments.  If no arguments
+        are given the context will be empty.  These two calls do the same::
+
+            template.render(knights='that say nih')
+            template.render({'knights': 'that say nih'})
+
+        This will return the rendered template as unicode string.
+        """
         try:
-            return u''.join(self.generate(*args, **kwargs))
+            return concat(self._generate(*args, **kwargs))
         except:
-            # hide the `generate` frame
-            exc_type, exc_value, tb = sys.exc_info()
-            raise exc_type, exc_value, tb.tb_next
+            exc_type, exc_value, tb = translate_exception(sys.exc_info())
+            raise exc_type, exc_value, tb
 
     def stream(self, *args, **kwargs):
-        """Return a `TemplateStream` that generates the template."""
+        """Works exactly like :meth:`generate` but returns a
+        :class:`TemplateStream`.
+        """
+        return TemplateStream(self.generate(*args, **kwargs))
+
+    def generate(self, *args, **kwargs):
+        """For very large templates it can be useful to not render the whole
+        template at once but evaluate each statement after another and yield
+        piece for piece.  This method basically does exactly that and returns
+        a generator that yields one item after another as unicode strings.
+
+        It accepts the same arguments as :meth:`render`.
+        """
         try:
-            return TemplateStream(self.generate(*args, **kwargs))
+            for item in self._generate(*args, **kwargs):
+                yield item
         except:
-            # hide the `generate` frame
-            exc_type, exc_value, tb = sys.exc_info()
-            raise exc_type, exc_value, tb.tb_next
+            exc_type, exc_value, tb = translate_exception(sys.exc_info())
+            raise exc_type, exc_value, tb
 
-    def generate(self, *args, **kwargs):
-        """Return a generator that generates the template."""
+    def _generate(self, *args, **kwargs):
         # assemble the context
         context = dict(*args, **kwargs)
 
@@ -335,27 +511,49 @@ class Template(object):
                                      'will lead to unexpected results.' %
                     (plural, ', '.join(overrides), plural or ' a', plural))
 
-        try:
-            for event in self.root_render_func(self.new_context(context)):
-                yield event
-        except:
-            exc_type, exc_value, tb = translate_exception(sys.exc_info())
-            raise exc_type, exc_value, tb
+        return self.root_render_func(self.new_context(context))
 
-    def new_context(self, vars):
-        """Create a new template context for this template."""
-        return TemplateContext(self.environment, dict(self.globals, **vars),
-                               self.name, self.blocks)
-
-    def include(self, context=None):
-        """Include this template."""
-        if context is None:
-            context = self.new_context({})
-        elif isinstance(context, TemplateContext):
-            context = self.new_context(context.get_root())
+    def new_context(self, vars=None, shared=False):
+        """Create a new template context for this template.  The vars
+        provided will be passed to the template.  Per default the globals
+        are added to the context, if shared is set to `True` the data
+        provided is used as parent namespace.  This is used to share the
+        same globals in multiple contexts without consuming more memory.
+        (This works because the context does not modify the parent dict)
+        """
+        if vars is None:
+            vars = {}
+        if shared:
+            parent = vars
         else:
-            context = self.new_context(context)
-        return IncludedTemplate(self, context)
+            parent = dict(self.globals, **vars)
+        return Context(self.environment, parent, self.name, self.blocks)
+
+    def make_module(self, vars=None, shared=False):
+        """This method works like the :attr:`module` attribute when called
+        without arguments but it will evaluate the template every call
+        rather then caching the template.  It's also possible to provide
+        a dict which is then used as context.  The arguments are the same
+        as fo the :meth:`new_context` method.
+        """
+        return TemplateModule(self, self.new_context(vars, shared))
+
+    @property
+    def module(self):
+        """The template as module.  This is used for imports in the
+        template runtime but is also useful if one wants to access
+        exported template variables from the Python layer:
+
+        >>> t = Template('{% macro foo() %}42{% endmacro %}23')
+        >>> unicode(t.module)
+        u'23'
+        >>> t.module.foo()
+        u'42'
+        """
+        if hasattr(self, '_module'):
+            return self._module
+        self._module = rv = self.make_module()
+        return rv
 
     def get_corresponding_lineno(self, lineno):
         """Return the source line number of a line number in the
@@ -380,35 +578,51 @@ class Template(object):
                 self._debug_info.split('&')]
 
     def __repr__(self):
-        return '<%s %r>' % (
-            self.__class__.__name__,
-            self.name or '<from string>'
-        )
+        if self.name is None:
+            name = 'memory:%x' % id(self)
+        else:
+            name = repr(self.name)
+        return '<%s %s>' % (self.__class__.__name__, name)
 
 
-class IncludedTemplate(object):
-    """Represents an included template."""
+class TemplateModule(object):
+    """Represents an imported template.  All the exported names of the
+    template are available as attributes on this object.  Additionally
+    converting it into an unicode- or bytestrings renders the contents.
+    """
 
     def __init__(self, template, context):
-        self._template = template
-        self._name = template.name
-        self._rendered_body = u''.join(template.root_render_func(context))
-        self._context = context.get_exported()
+        # don't alter this attribute unless you change it in the
+        # compiler too.  The Include without context passing directly
+        # uses the mangled name.  The reason why we use a mangled one
+        # is to avoid name clashes with macros with those names.
+        self.__body_stream = list(template.root_render_func(context))
+        self.__dict__.update(context.get_exported())
+        self.__name__ = template.name
 
-    __getitem__ = lambda x, n: x._context[n]
-    __html__ = __unicode__ = lambda x: x._rendered_body
+    __html__ = lambda x: Markup(concat(x.__body_stream))
+    __unicode__ = lambda x: unicode(concat(x.__body_stream))
+
+    def __str__(self):
+        return unicode(self).encode('utf-8')
 
     def __repr__(self):
-        return '<%s %r>' % (
-            self.__class__.__name__,
-            self._name
-        )
+        if self.__name__ is None:
+            name = 'memory:%x' % id(self)
+        else:
+            name = repr(self.name)
+        return '<%s %s>' % (self.__class__.__name__, name)
 
 
 class TemplateStream(object):
-    """This class wraps a generator returned from `Template.generate` so that
-    it's possible to buffer multiple elements so that it's possible to return
-    them from a WSGI application which flushes after each iteration.
+    """A template stream works pretty much like an ordinary python generator
+    but it can buffer multiple items to reduce the number of total iterations.
+    Per default the output is unbuffered which means that for every unbuffered
+    instruction in the template one unicode string is yielded.
+
+    If buffering is enabled with a buffer size of 5, five items are combined
+    into a new unicode string.  This is mainly useful if you are streaming
+    big templates to a client via WSGI which flushes after each iteration.
     """
 
     def __init__(self, gen):
@@ -422,7 +636,7 @@ class TemplateStream(object):
         self.buffered = False
 
     def enable_buffering(self, size=5):
-        """Enable buffering. Buffer `size` items before yielding them."""
+        """Enable buffering.  Buffer `size` items before yielding them."""
         if size <= 1:
             raise ValueError('buffer size too small')
 
@@ -439,8 +653,8 @@ class TemplateStream(object):
                         c_size += 1
                 except StopIteration:
                     if not c_size:
-                        raise
-                yield u''.join(buf)
+                        return
+                yield concat(buf)
                 del buf[:]
                 c_size = 0