[svn] moved some of the documentation into docstrings
authorArmin Ronacher <armin.ronacher@active-4.com>
Thu, 5 Apr 2007 09:21:38 +0000 (11:21 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Thu, 5 Apr 2007 09:21:38 +0000 (11:21 +0200)
--HG--
branch : trunk

13 files changed:
CHANGES
docs/generate.py
docs/src/devintro.txt
jinja/datastructure.py
jinja/defaults.py
jinja/environment.py
jinja/filters.py
jinja/lexer.py
jinja/nodes.py
jinja/parser.py
jinja/translators/__init__.py
jinja/translators/python.py
jinja/utils.py

diff --git a/CHANGES b/CHANGES
index 435d009da43631ae538b135dac6046f3eebeed7e..77aee3f4375a3fd43b7a0cc1c35e0054a6710918 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -15,7 +15,7 @@ Version 1.1
 
 - improved security system regarding function calls.
 
-- added `lipsum` function
+- added `lipsum` function to generate random text.
 
 - strings without unicode characters are processed as binary strings now
   to workaround problems with `datetime.strftime` which only accepts
@@ -25,6 +25,12 @@ Version 1.1
 
 - silent variable name failure is now toggleable
 
+- fixed issue with old-style classes not implementing `__getitem__`
+  (thanks to Axel Böhm for discovering that bug)
+
+- added a bunch of new docstrings to the Jinja classes. Makes fun now to
+  use pydoc :-)
+
 
 Version 1.0
 -----------
index a6dbb4e432ce863ea232d166bb56a3b415b67e1d..9ae42f4a98b1c21b540f2f6e35a1ebb968ebd1c5 100755 (executable)
@@ -101,6 +101,13 @@ def generate_list_of_loaders():
 
     return '\n\n'.join(result)
 
+def generate_environment_doc():
+    from jinja.environment import Environment
+    return '%s\n\n%s' % (
+        inspect.getdoc(Environment),
+        inspect.getdoc(Environment.__init__)
+    )
+
 e = Environment()
 
 PYGMENTS_FORMATTER = HtmlFormatter(style='pastie', cssclass='syntax')
@@ -108,6 +115,7 @@ 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()
+ENVIRONMENT_DOC = generate_environment_doc()
 
 FULL_TEMPLATE = e.from_string('''\
 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
@@ -229,7 +237,8 @@ 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)
+               .replace('[[list_of_loaders]]', LIST_OF_LOADERS)\
+               .replace('[[environment_doc]]', ENVIRONMENT_DOC)
     parts = publish_parts(
         data,
         writer=writer,
index ea7819a747ddfa5e5c463ed5958425b11842aad0..9bf7b41c237caa85dcd4557f77e72a9c6db4bfef 100644 (file)
@@ -27,67 +27,7 @@ a look at the `installation`_ page for troubleshooting.
 The Environment
 ===============
 
-The core component of Jinja is the `Environment`. It contains important shared
-variables like configuration, filters, tests, globals and other stuff.
-
-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 ``'#}'``.
-`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.
-                          If `auto_escape` is ``True`` there will be also
-                          a ``Markup`` object in the template namespace
-                          to define partial html fragments. Note that we do
-                          not recomment this feature, see also the comment
-                          below.
-`default_filters`         list of tuples in the form (``filter_name``,
-                          ``arguments``) where ``filter_name`` is the
-                          name of a registered filter and ``arguments``
-                          a tuple with the filter arguments. The filters
-                          specified here will always be applied when
-                          printing data to the template.
-                          *new in jinja 1.1*
-`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.
-`context_class`           the context class this template should use. See
-                          the `context documentation`_ for more details.
-`silent`                  set this to `False` if you want to receive errors
-                          for missing template variables or attributes.
-                          Defaults to `False`. *new in Jinja 1.1*
-`friendly_traceback`      Set this to `False` to disable the developer
-                          friendly traceback rewriting. Whenever an
-                          runtime or syntax error occours jinja will try
-                          to make a developer friendly traceback that shows
-                          the error in the template line. This however can
-                          be annoying when debugging broken functions which
-                          are called from the template. *new in Jinja 1.1*
-========================= ==================================================
-
-All of these variables except those marked with a star (*) are modifiable after
-environment initialization.
+[[environment_doc]]
 
 The environment provides the following useful functions and properties in
 addition to the initialization values:
@@ -174,7 +114,7 @@ Writing tests is explained in the `test development`_ section.
 
 .. _installation: installation.txt
 .. _context documentation: contextenv.txt
-.. _translators: translators.txt
 .. _loader: loaders.txt
+.. _translators: translators.txt
 .. _filter development: filters.txt
 .. _test development: tests.txt
index 908a1c62a961a46bf01843eb58fc38a8b6ef6497..7ccd64f3461f1b302527c1f075d152d2fcf948a3 100644 (file)
@@ -44,56 +44,83 @@ class UndefinedType(object):
     __slots__ = ()
 
     def __init__(self):
-        try:
-            Undefined
-        except NameError:
-            pass
-        else:
-            raise TypeError('cannot create %r instances' %
-                            self.__class__.__name__)
+        raise TypeError('cannot create %r instances' %
+                        self.__class__.__name__)
 
-    __sub__ = __mul__ = __div__ = __rsub__ = __rmul__ = __div__ = __radd__ = \
-    __add__ = lambda self, other: other
+    def __add__(self, other):
+        """Any operator returns the operand."""
+        return other
+    __sub__ = __mul__ = __div__ = __rsub__ = __rmul__ = __div__ = __mod__ =\
+    __radd__ = __rmod__ = __add__
 
     def __getitem__(self, arg):
+        """Getting any item returns `Undefined`"""
         return self
 
     def __iter__(self):
+        """Iterating over `Undefined` returns an empty iterator."""
         if False:
             yield None
 
     def __getattr__(self, arg):
+        """Getting any attribute returns `Undefined`"""
         return self
 
     def __nonzero__(self):
+        """`Undefined` is considered boolean `False`"""
         return False
 
     def __len__(self):
+        """`Undefined` is an empty sequence"""
         return 0
 
     def __str__(self):
+        """The string representation is empty."""
         return ''
 
     def __unicode__(self):
+        """The unicode representation is empty."""
         return u''
 
+    def __repr__(self):
+        """The representation is ``'Undefined'``"""
+        return 'Undefined'
+
     def __int__(self):
+        """Converting `Undefined` to an integer ends up in ``0``"""
         return 0
 
     def __float__(self):
+        """Converting `Undefined` to an float ends up in ``0.0``"""
         return 0.0
 
     def __eq__(self, other):
+        """`Undefined` is not equal to anything else."""
         return False
 
     def __ne__(self, other):
+        """`Undefined` is not equal to anything else."""
         return True
 
     def __call__(self, *args, **kwargs):
+        """Calling `Undefined` returns `Undefined`"""
+        return self
+
+    def __copy__(self):
+        """Return a copy."""
         return self
 
+    def __deepcopy__(self, memo):
+        """Return a deepcopy."""
+        return self
 
-Undefined = UndefinedType()
+    def __reduce__(self):
+        """Helper for pickle."""
+        return 'Undefined'
+
+
+#: the singleton instance of UndefinedType
+Undefined = object.__new__(UndefinedType)
 
 
 class FakeTranslator(object):
@@ -102,9 +129,15 @@ class FakeTranslator(object):
     """
 
     def gettext(self, s):
+        """
+        Translate a singular string.
+        """
         return s
 
     def ngettext(self, s, p, n):
+        """
+        Translate a plural string.
+        """
         if n == 1:
             return s
         return p
@@ -153,20 +186,26 @@ class Context(object):
         self.cache = {}
 
     def pop(self):
-        """Pop the last layer from the stack and return it."""
+        """
+        Pop the last layer from the stack and return it.
+        """
         rv = self._stack.pop()
         self.current = self._stack[-1]
         return rv
 
     def push(self, data=None):
-        """Push a new dict or empty layer to the stack and return that layer"""
+        """
+        Push a new dict or empty layer to the stack and return that layer
+        """
         data = data or {}
         self._stack.append(data)
         self.current = self._stack[-1]
         return data
 
     def to_dict(self):
-        """Convert the context into a dict. This skips the globals."""
+        """
+        Convert the context into a dict. This skips the globals.
+        """
         result = {}
         for layer in self._stack[1:]:
             for key, value in layer.iteritems():
@@ -176,6 +215,10 @@ class Context(object):
         return result
 
     def __getitem__(self, name):
+        """
+        Resolve one item. Restrict the access to internal variables
+        such as ``'::cycle1'``. Resolve deferreds.
+        """
         if not name.startswith('::'):
             # because the stack is usually quite small we better use [::-1]
             # which is faster than reversed() somehow.
@@ -195,24 +238,32 @@ class Context(object):
         raise TemplateRuntimeError('%r is not defined' % name)
 
     def __setitem__(self, name, value):
+        """
+        Set a variable in the outermost layer.
+        """
         self.current[name] = value
 
     def __delitem__(self, name):
+        """
+        Delete an variable in the outermost layer.
+        """
         if name in self.current:
             del self.current[name]
 
     def __contains__(self, name):
+        """
+        Check if the context contains a given variable.
+        """
         for layer in self._stack:
             if name in layer:
                 return True
         return False
 
     def __repr__(self):
-        tmp = {}
-        for d in self._stack:
-            for key, value in d.iteritems():
-                tmp[key] = value
-        return 'Context(%s)' % repr(tmp)
+        """
+        String representation of the context.
+        """
+        return 'Context(%r)' % self.to_dict()
 
 
 class LoopContext(object):
@@ -349,11 +400,12 @@ class TokenStream(object):
     """
 
     def __init__(self, generator):
-        self._generator = generator
+        self._next = generator.next
         self._pushed = []
         self.last = (1, 'initial', '')
 
     def __iter__(self):
+        """Return self in order to mark this is iterator."""
         return self
 
     def __nonzero__(self):
@@ -373,7 +425,7 @@ class TokenStream(object):
         if self._pushed:
             rv = self._pushed.pop()
         else:
-            rv = self._generator.next()
+            rv = self._next()
         self.last = rv
         return rv
 
index b0ca6ef4f7046d52fbe7492056df2f0131a76575..320a54fec166cd8bd4a0105599bc625039b20c5f 100644 (file)
@@ -14,6 +14,9 @@ from jinja.utils import debug_context, safe_range, generate_lorem_ipsum, \
      watch_changes
 
 
+__all__ = ['DEFAULT_FILTERS', 'DEFAULT_TESTS', 'DEFAULT_NAMESPACE']
+
+
 DEFAULT_NAMESPACE = {
     'range':                safe_range,
     'debug':                debug_context,
index 600328f4cbcd3c9ae305bcb605143fa6204c3457..ed75a06aa2f0844f937a976a885eae5e631a0f64 100644 (file)
@@ -19,9 +19,16 @@ from jinja.exceptions import FilterNotFound, TestNotFound, \
 from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
 
 
+__all__ = ['Environment']
+
+
 class Environment(object):
     """
     The jinja environment.
+
+    The core component of Jinja is the `Environment`. It contains
+    important shared variables like configuration, filters, tests,
+    globals and others.
     """
 
     def __init__(self,
@@ -43,6 +50,70 @@ class Environment(object):
                  context_class=Context,
                  silent=True,
                  friendly_traceback=True):
+        """
+        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 ``'#}'``.
+        `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. If
+                                  `auto_escape` is ``True`` there will be also
+                                  a ``Markup`` object in the template
+                                  namespace to define partial html fragments.
+                                  Note that we do not recomment this feature.
+        `default_filters`         list of tuples in the form (``filter_name``,
+                                  ``arguments``) where ``filter_name`` is the
+                                  name of a registered filter and
+                                  ``arguments`` a tuple with the filter
+                                  arguments. The filters specified here will
+                                  always be applied when printing data to the
+                                  template. *new in Jinja 1.1*
+        `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.
+        `context_class`           the context class this template should use.
+                                  See the `Context` documentation for more
+                                  details.
+        `silent`                  set this to `False` if you want to receive
+                                  errors for missing template variables or
+                                  attributes. Defaults to `False`. *new in
+                                  Jinja 1.1*
+        `friendly_traceback`      Set this to `False` to disable the developer
+                                  friendly traceback rewriting. Whenever an
+                                  runtime or syntax error occours jinja will
+                                  try to make a developer friendly traceback
+                                  that shows the error in the template line.
+                                  This however can be annoying when debugging
+                                  broken functions that are called from the
+                                  template. *new in Jinja 1.1*
+        ========================= ============================================
+
+        All of these variables except those marked with a star (*) are
+        modifiable after environment initialization.
+        """
 
         # lexer / parser information
         self.block_start_string = block_start_string
@@ -81,15 +152,25 @@ 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, doc=loader.__doc__)
 
     def parse(self, source, filename=None):
-        """Function that creates a new parser and parses the source."""
+        """
+        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.
+
+        .. _translators: translators.txt
+        """
         parser = Parser(self, source, filename)
         return parser.parse()
 
     def from_string(self, source):
-        """Load a template from a string."""
+        """
+        Load and parse a template source and translate it into eval-able
+        Python code. This code is wrapped within a `Template` class that
+        allows you to render it.
+        """
         from jinja.parser import Parser
         from jinja.translators.python import PythonTranslator
         try:
@@ -108,8 +189,10 @@ class Environment(object):
             return rv
 
     def get_template(self, filename):
-        """Load a template from a filename. Only works
-        if a proper loader is set."""
+        """
+        Load a template from a loader. If the template does not exist, you
+        will get a `TemplateNotFound` exception.
+        """
         return self._loader.load(filename)
 
     def to_unicode(self, value):
index 197dd3eab3c0f612a2ff1f73152978e32837b1ba..971d99b5f3cf5400a91071784461baca843061e7 100644 (file)
@@ -269,7 +269,9 @@ def do_first():
         try:
             return iter(seq).next()
         except StopIteration:
-            return Undefined
+            if env.silent:
+                return Undefined
+            raise TemplateRuntimeError('%r is empty' % seq)
     return wrapped
 
 
@@ -280,8 +282,10 @@ def do_last():
     def wrapped(env, context, seq):
         try:
             return iter(_reversed(seq)).next()
-        except (TypeError, StopIteration):
-            return Undefined
+        except StopIteration:
+            if env.silent:
+                return Undefined
+            raise TemplateRuntimeError('%r is empty' % seq)
     return wrapped
 
 
@@ -292,8 +296,10 @@ def do_random():
     def wrapped(env, context, seq):
         try:
             return choice(seq)
-        except:
-            return Undefined
+        except IndexError:
+            if env.silent:
+                return Undefined
+            raise TemplateRuntimeError('%r is empty' % seq)
     return wrapped
 
 
index 413bdc5a892075a8254ee94b8a22133c5625d319..ec5052c5a59ce2e583aae375f082b628f5ddc7cc 100644 (file)
@@ -3,6 +3,22 @@
     jinja.lexer
     ~~~~~~~~~~~
 
+    This module implements a Jinja / Python combination lexer. The
+    `Lexer` class provided by this module is used to do some preprocessing
+    for Jinja.
+
+    On the one hand it filters out invalid operators like the bitshift
+    operators we don't allow in templates. On the other hand it separates
+    template code and python code in expressions.
+
+    Because of some limitations in the compiler package which are just
+    natural but annoying for Jinja, the lexer also "escapes" non names that
+    are not keywords. The Jinja parser then removes those escaping marks
+    again.
+
+    This is required in order to make "class" and some other python keywords
+    we don't use valid identifiers.
+
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
@@ -16,6 +32,9 @@ except NameError:
     from sets import Set as set
 
 
+__all__ = ['Lexer', 'Failure', 'keywords']
+
+
 # static regular expressions
 whitespace_re = re.compile(r'\s+(?m)')
 name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
@@ -155,12 +174,12 @@ class Lexer(object):
 
         Additionally non keywords are escaped.
         """
-        def filter():
+        def generate():
             for lineno, token, value in self.tokeniter(source):
                 if token == 'name' and value not in keywords:
                     value += '_'
                 yield lineno, token, value
-        return TokenStream(filter())
+        return TokenStream(generate())
 
     def tokeniter(self, source):
         """
@@ -170,7 +189,7 @@ class Lexer(object):
         parser uses the `tokenize` function with returns a `TokenStream` with
         some escaped tokens.
         """
-        source = type(source)('\n').join(source.splitlines())
+        source = '\n'.join(source.splitlines())
         pos = 0
         lineno = 1
         stack = ['root']
index c06ab8d7e70d10edfb6035fdf84bfba2e3668561..9156b3b957dc0691758c752a708f2495171ab267 100644 (file)
@@ -3,7 +3,11 @@
     jinja.nodes
     ~~~~~~~~~~~
 
-    Additional nodes for Jinja. Look like nodes from the ast.
+    This module implements additional nodes derived from the ast base node.
+
+    It also provides some node tree helper functions like `in_lineno` and
+    `get_nodes` used by the parser and translator in order to normalize
+    python and jinja nodes.
 
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
index 8449cd83d19265b2a9e6963d1640070f9110251f..ec09e60cd97fb8e21e92e32afc9557df8cfd2a46 100644 (file)
@@ -5,6 +5,12 @@
 
     Implements the template parser.
 
+    The Jinja template parser is not a real parser but a combination of the
+    python compiler package and some postprocessing. The tokens yielded by
+    the lexer are used to separate template data and expressions. The
+    expression tokens are then converted into strings again and processed
+    by the python parser.
+
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
@@ -20,6 +26,9 @@ except NameError:
     from sets import Set as set
 
 
+__all__ = ['Parser']
+
+
 # callback functions for the subparse method
 end_of_block = lambda p, t, d: t == 'block_end'
 end_of_variable = lambda p, t, d: t == 'variable_end'
@@ -34,24 +43,6 @@ end_of_block_tag = lambda p, t, d: t == 'name' and d == 'endblock'
 end_of_trans = lambda p, t, d: t == 'name' and d == 'endtrans'
 
 
-string_inc_re = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+')
-
-
-def inc_string(s):
-    """
-    Increment a string
-    """
-    m = string_inc_re.search(s)
-    if m:
-        next = str(int(m.group(1)) + 1)
-        start, end = m.span(1)
-        s = s[:max(end - len(next), start)] + next + s[end:]
-    else:
-        name, ext = s.rsplit('.', 1)
-        return '%s2.%s' % (name, ext)
-    return s
-
-
 class Parser(object):
     """
     The template parser class.
@@ -67,7 +58,10 @@ class Parser(object):
         self.filename = filename
         self.tokenstream = environment.lexer.tokenize(source)
 
+        #: if this template has a parent template it's stored here
+        #: after parsing
         self.extends = None
+        #: set for blocks in order to keep them unique
         self.blocks = set()
 
         self.directives = {
index 1b0e37c6ed39e78f5d7698eb8d70c39eeb44ae48..7576a0e47ba9a4da96cebb10fbfc464d41442d5f 100644 (file)
@@ -3,8 +3,7 @@
     jinja.translators
     ~~~~~~~~~~~~~~~~~
 
-    The submodules of this module provide translators for the jinja ast
-    which basically just is the python ast with a few more nodes.
+    The submodules of this module provide translators for the jinja ast.
 
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
index 3f3133c35e3b83ebd2a87bddfeccfdda30b8ae60..ede4737294399de362b9aafb98f3caf36e9d0821 100644 (file)
@@ -5,6 +5,17 @@
 
     This module translates a jinja ast into python code.
 
+    This translator tries hard to keep Jinja sandboxed. All security
+    relevant calls are wrapped by methods defined in the environment.
+    This affects:
+
+    - method calls
+    - attribute access
+    - name resolution
+
+    It also adds debug symbols used by the traceback toolkit implemented
+    in `jinja.utils`.
+
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
@@ -108,6 +119,10 @@ class PythonTranslator(Translator):
         self.environment = environment
         self.node = node
 
+        #: mapping to constants in the environment that are not
+        #: converted to context lookups. this should improve
+        #: performance and avoid accidentally screwing things up
+        #: by rebinding essential constants.
         self.constants = {
             'true':                 'True',
             'false':                'False',
@@ -115,6 +130,11 @@ class PythonTranslator(Translator):
             'undefined':            'Undefined'
         }
 
+        #: bind the nodes to the callback functions. There are
+        #: some missing! A few are specified in the `unhandled`
+        #: mapping in order to disallow their usage, some of them
+        #: will not appear in the jinja parser output because
+        #: they are filtered out.
         self.handlers = {
             # jinja nodes
             nodes.Template:         self.handle_template,
@@ -130,7 +150,7 @@ class PythonTranslator(Translator):
             nodes.Block:            self.handle_block,
             nodes.Include:          self.handle_include,
             nodes.Trans:            self.handle_trans,
-            # used python nodes
+            # python nodes
             ast.Name:               self.handle_name,
             ast.AssName:            self.handle_name,
             ast.Compare:            self.handle_compare,
@@ -158,33 +178,43 @@ class PythonTranslator(Translator):
             ast.Sliceobj:           self.handle_sliceobj
         }
 
+        #: mapping of unsupported syntax elements.
+        #: the value represents the feature name that appears
+        #: in the exception.
         self.unsupported = {
             ast.ListComp:           'list comprehensions',
             ast.From:               'imports',
             ast.Import:             'imports',
         }
+
+        #: because of python2.3 compatibility add generator
+        #: expressions only to the list of unused features
+        #: if it exists.
         if hasattr(ast, 'GenExpr'):
             self.unsupported.update({
                 ast.GenExpr:        'generator expressions'
             })
 
-        self.require_translations = False
-
     # -- public methods
 
     def process(environment, node):
+        """
+        The only public method. Creates a translator instance,
+        translates the code and returns it in form of an
+        `Template` instance.
+        """
         translator = PythonTranslator(environment, node)
         filename = node.filename or '<template>'
         source = translator.translate()
-        return Template(environment,
-                        compile(source, filename, 'exec'))
+        return Template(environment, compile(source, filename, 'exec'))
     process = staticmethod(process)
 
     # -- private helper methods
 
     def indent(self, text):
         """
-        Indent the current text.
+        Indent the current text. This does only indent the
+        first line.
         """
         return (' ' * (self.indention * 4)) + text
 
@@ -236,7 +266,9 @@ class PythonTranslator(Translator):
 
     def handle_node(self, node):
         """
-        Handle one node
+        Handle one node. Resolves the correct callback functions defined
+        in the callback mapping. This also raises the `TemplateSyntaxError`
+        for unsupported syntax elements such as list comprehensions.
         """
         if node.__class__ in self.handlers:
             out = self.handlers[node.__class__](node)
@@ -249,10 +281,19 @@ class PythonTranslator(Translator):
         return out
 
     def reset(self):
+        """
+        Reset translation variables such as indention, cycle id
+        or the require_translations flag.
+        """
         self.indention = 0
         self.last_cycle_id = 0
+        self.require_translations = False
 
     def translate(self):
+        """
+        Translate the node defined in the constructor after
+        resetting the translator.
+        """
         self.reset()
         return self.handle_node(self.node)
 
@@ -377,6 +418,9 @@ class PythonTranslator(Translator):
                 str(name),
                 _to_tuple(tmp)
             ))
+
+        # blocks must always be defined. even if it's empty. some
+        # features depend on it
         lines.append('\nblocks = {\n%s\n}' % ',\n'.join(dict_lines))
 
         # now get the real source lines and map the debugging symbols
@@ -439,8 +483,8 @@ class PythonTranslator(Translator):
 
         # simple loops
         else:
-            write('context[\'loop\'] = loop = LoopContext(%s, context[\'loop\'], None)' %
-                  self.handle_node(node.seq))
+            write('context[\'loop\'] = loop = LoopContext(%s, '
+                  'context[\'loop\'], None)' % self.handle_node(node.seq))
             write('for %s in loop:' %
                 self.handle_node(node.item)
             )
@@ -469,7 +513,8 @@ class PythonTranslator(Translator):
             self.indention += 1
             write('yield None')
             self.indention -= 2
-            write('context[\'loop\'] = LoopContext(None, context[\'loop\'], buffereater(forloop))')
+            write('context[\'loop\'] = LoopContext(None, context[\'loop\'], '
+                  'buffereater(forloop))')
             write('for item in forloop(%s):' % self.handle_node(node.seq))
             self.indention += 1
             write('yield item')
@@ -526,9 +571,11 @@ class PythonTranslator(Translator):
         self.indention -= 1
 
         if hardcoded:
-            write('yield finish_var(context.current[%r].cycle(), context)' % name)
+            write('yield finish_var(context.current[%r].cycle(), '
+                  'context)' % name)
         else:
-            write('yield finish_var(context.current[%r].cycle(%s), context)' % (
+            write('yield finish_var(context.current[%r].cycle(%s), '
+                  'context)' % (
                 name,
                 self.handle_node(node.seq)
             ))
@@ -558,7 +605,8 @@ class PythonTranslator(Translator):
             write('argcount = len(args)')
             tmp = []
             for idx, (name, n) in enumerate(node.arguments):
-                tmp.append('\'%s\': (argcount > %d and (args[%d],) or (%s,))[0]' % (
+                tmp.append('\'%s\': (argcount > %d and (args[%d],) '
+                           'or (%s,))[0]' % (
                     name,
                     idx,
                     idx,
@@ -575,7 +623,8 @@ class PythonTranslator(Translator):
         self.indention += 1
         write('yield False')
         self.indention -= 2
-        buf.append(self.indent('context[%r] = buffereater(macro)' % node.name))
+        buf.append(self.indent('context[%r] = buffereater(macro)' %
+                               node.name))
 
         return '\n'.join(buf)
 
@@ -605,7 +654,8 @@ class PythonTranslator(Translator):
         self.indention += 1
         write('yield None')
         self.indention -= 2
-        write('yield %s' % self.filter('u\'\'.join(filtered())', node.filters))
+        write('yield %s' % self.filter('u\'\'.join(filtered())',
+                                       node.filters))
         return '\n'.join(buf)
 
     def handle_block(self, node, level=0):
@@ -790,7 +840,8 @@ class PythonTranslator(Translator):
             else:
                 args.append(self.handle_node(arg))
         if not (args or kwargs or star_args or dstar_args):
-            return 'call_function_simple(%s, context)' % self.handle_node(node.node)
+            return 'call_function_simple(%s, context)' % \
+                   self.handle_node(node.node)
         return 'call_function(%s, context, %s, {%s}, %s, %s)' % (
             self.handle_node(node.node),
             _to_tuple(args),
index 2b0a4cfebf0b498b277d4d1a114de22d8ca5be49..cb219862f9993f592f413058152ca544bf3214e6 100644 (file)
@@ -21,6 +21,9 @@ from jinja.nodes import Trans
 from jinja.datastructure import Context, TemplateData
 from jinja.exceptions import SecurityException, TemplateNotFound
 
+#: the python2.4 version of deque is missing the remove method
+#: because a for loop with a lookup for the missing value written
+#: in python is slower we just use deque if we have python2.5 or higher
 if sys.version_info >= (2, 5):
     from collections import deque
 else:
@@ -316,7 +319,10 @@ def raise_syntax_error(exception, env, source=None):
 
 def collect_translations(ast):
     """
-    Collect all translatable strings for the given ast.
+    Collect all translatable strings for the given ast. The
+    return value is a list of tuples in the form ``(lineno, singular,
+    plural)``. If a translation doesn't require a plural form the
+    third item is `None`.
     """
     todo = [ast]
     result = []
@@ -428,23 +434,36 @@ class CacheDict(object):
         self._append = self._queue.append
 
     def copy(self):
+        """
+        Return an shallow copy of the instance.
+        """
         rv = CacheDict(self.capacity)
         rv._mapping.update(self._mapping)
         rv._queue = self._queue[:]
         return rv
 
     def get(self, key, default=None):
+        """
+        Return an item from the cache dict or `default`
+        """
         if key in self:
             return self[key]
         return default
 
     def setdefault(self, key, default=None):
+        """
+        Set `default` if the key is not in the cache otherwise
+        leave unchanged. Return the value of this key.
+        """
         if key in self:
             return self[key]
         self[key] = default
         return default
 
     def clear(self):
+        """
+        Clear the cache dict.
+        """
         self._mapping.clear()
         try:
             self._queue.clear()
@@ -452,9 +471,15 @@ class CacheDict(object):
             del self._queue[:]
 
     def __contains__(self, key):
+        """
+        Check if a key exists in this cache dict.
+        """
         return key in self._mapping
 
     def __len__(self):
+        """
+        Return the current size of the cache dict.
+        """
         return len(self._mapping)
 
     def __repr__(self):
@@ -464,6 +489,12 @@ class CacheDict(object):
         )
 
     def __getitem__(self, key):
+        """
+        Get an item from the cache dict. Moves the item up so that
+        it has the highest priority then.
+
+        Raise an `KeyError` if it does not exist.
+        """
         rv = self._mapping[key]
         if self._queue[-1] != key:
             self._remove(key)
@@ -471,6 +502,10 @@ class CacheDict(object):
         return rv
 
     def __setitem__(self, key, value):
+        """
+        Sets the value for an item. Moves the item up so that it
+        has the highest priority then.
+        """
         if key in self._mapping:
             self._remove(key)
         elif len(self._mapping) == self.capacity:
@@ -479,16 +514,38 @@ class CacheDict(object):
         self._mapping[key] = value
 
     def __delitem__(self, key):
+        """
+        Remove an item from the cache dict.
+        Raise an `KeyError` if it does not exist.
+        """
         del self._mapping[key]
         self._remove(key)
 
     def __iter__(self):
+        """
+        Iterate over all values in the cache dict, ordered by
+        the most recent usage.
+        """
         try:
             return reversed(self._queue)
         except NameError:
             return iter(self._queue[::-1])
 
     def __reversed__(self):
+        """
+        Iterate over the values in the cache dict, oldest items
+        coming first.
+        """
         return iter(self._queue)
 
     __copy__ = copy
+
+    def __deepcopy__(self):
+        """
+        Return a deep copy of the cache dict.
+        """
+        from copy import deepcopy
+        rv = CacheDict(self.capacity)
+        rv._mapping = deepcopy(self._mapping)
+        rv._queue = deepcopy(self._queue)
+        return rv