[svn] doc changes + changes in the i18n system of jinja
authorArmin Ronacher <armin.ronacher@active-4.com>
Wed, 14 Mar 2007 16:34:34 +0000 (17:34 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Wed, 14 Mar 2007 16:34:34 +0000 (17:34 +0100)
--HG--
branch : trunk

docs/src/designerdoc.txt
docs/src/index.txt
docs/src/recipies.txt [new file with mode: 0644]
jinja/datastructure.py
jinja/defaults.py
jinja/environment.py
jinja/translators/python.py
jinja/utils.py
www/download.html
www/index.html

index bc537504ca65fd44aa9b953ec1755523bb0cbde1..7f0c96cf1bbd3529ed67ea4c58bbcf5f17caf731 100644 (file)
@@ -493,6 +493,48 @@ template but outside of a visible block (thus outside of any block) will be
 available in all blocks below. This allows you to use `include` statements to
 load often used macros at once.
 
+Undefined Variables
+===================
+
+If you have already worked with python you probably know about the fact that
+undefined variables raise an exception. This is different in Jinja. There is a
+special value called `undefined` that represents values that do not exist.
+
+This special variable works complete different from any variables you maybe
+know. If you print it using ``{{ variable }}`` it will not appear because it's
+literally empty. If you try to iterate over it, it will work. But no items
+are returned. Comparing this value to any other value results in `false`.
+Even if you compare it to itself:
+
+.. sourcecode:: jinja
+
+    {{ undefined == undefined }}
+        will return false. Not even undefined is undefined :)
+        Use `is defined` / `is not defined`:
+
+    {{ undefined is not defined }}
+        will return true.
+
+There are also some additional rules regarding this special value. Any
+mathematical operators (``+``, ``-``, ``*``, ``/``) return the operand
+as result:
+
+.. sourcecode:: jinja
+
+    {{ undefined + "foo" }}
+        returns "foo"
+
+    {{ undefined - 42 }}
+        returns 42. Note: not -42!
+
+In any expression `undefined` evaluates to `false`. It has no length, all
+attribute calls return undefined, calling too:
+
+.. sourcecode:: jinja
+
+    {{ undefined.attribute().attribute_too[42] }}
+        still returns `undefined`.
+
 Internationalization
 ====================
 
index a826c2b764235232f53b0e9c7a557033abbcce92..21a012b05fb94843eff362cc17180a56ad0dc617 100644 (file)
@@ -25,3 +25,5 @@ Welcome in the Jinja documentation.
   - `Syntax Reference <designerdoc.txt>`_
 
   - `Differences To Django <fromdjango.txt>`_
+
+  - `Recipies <recipies.txt>`_
diff --git a/docs/src/recipies.txt b/docs/src/recipies.txt
new file mode 100644 (file)
index 0000000..f444f00
--- /dev/null
@@ -0,0 +1,55 @@
+========
+Recipies
+========
+
+Here are some typical recipes for the usage of Jinja templates.
+
+Alternating Rows
+================
+
+If you want to have different styles for each row of a table or
+list you can use the ``cycle`` tag:
+
+.. sourcecode:: html+jinja
+
+    <ul>
+    {% for row in sequence %}
+      <li class="{% cycle 'even', 'odd' %}">{{ row|e }}</li>
+    {% endfor %}
+    </ul>
+
+``cycle`` can take an unlimited amount of strings. Each time this
+tag is encountered the next item from the list is rendered.
+If you pass it just one argument it's meant to be a sequence.
+
+Active Menu Item
+================
+
+Often you want to have a navigation bar with an active navigation
+item. This is really simple to achieve. Because ``set`` tags outside
+of ``blocks`` are global you can do something like this:
+
+**layout.html**:
+
+    .. sourcecode:: html+jinja
+
+        {% set navigation_bar = [
+            ('/', 'index', 'Index'),
+            ('/downloads/', 'downloads', 'Downloads'),
+            ('/about/', 'about', 'About')
+        ] %}
+        ...
+        <ul id="navigation">
+        {% for href, id, caption in navigation_bar %}
+          <li{% if id == active_page %} class="active"{% endif
+          %}><a href="{{ href|e }}">{{ caption|e }}</a>/li>
+        {% endfor %}
+        </ul>
+        ...
+
+**index.html**:
+
+    .. sourcecode:: jinja
+
+        {% extends "layout.html" %}
+        {% set active_page = "index" %}
index c15519f40a9c6352f442f0b9e508b7a26ac064e5..9871b55f376f460bd44a88e61c3c17ec3c30d4d0 100644 (file)
@@ -18,6 +18,22 @@ except NameError:
 from jinja.exceptions import TemplateRuntimeError
 
 
+def contextcallable(f):
+    """
+    Mark a function context callable.
+    """
+    f.jinja_context_callable = True
+    return f
+
+
+def unsafe(f):
+    """
+    Mark function as unsafe.
+    """
+    f.jinja_unsafe_call = True
+    return f
+
+
 class UndefinedType(object):
     """
     An object that does not exist.
@@ -62,7 +78,13 @@ class UndefinedType(object):
         return 0
 
     def __float__(self):
-        return 1
+        return 0.0
+
+    def __eq__(self, other):
+        return False
+
+    def __ne__(self, other):
+        return True
 
     def __call__(self, *args, **kwargs):
         return self
@@ -121,10 +143,6 @@ class Context(object):
         # cache object used for filters and tests
         self.cache = {}
 
-    def get_translator(self):
-        """Return the translator for i18n."""
-        return FakeTranslator()
-
     def pop(self):
         """Pop the last layer from the stack and return it."""
         rv = self._stack.pop()
index ddcc82b5b37a1b329fdd1b32d7f5fd589b4d7c7d..b634dad6f66ec6c088e2ef80ddfcff8e0f117a87 100644 (file)
 """
 from jinja.filters import FILTERS as DEFAULT_FILTERS
 from jinja.tests import TESTS as DEFAULT_TESTS
-from jinja.utils import debug_context
+from jinja.utils import debug_context, safe_range
 
 
 DEFAULT_NAMESPACE = {
-    'range':                range,
+    'range':                safe_range,
     'debug':                debug_context
 }
index 78862b80a53e254a32fd08666eb371bc08653a19..ee988b2027fb0937afa5ce6b62d509cdf87b6397 100644 (file)
@@ -12,7 +12,7 @@ import re
 from jinja.lexer import Lexer
 from jinja.parser import Parser
 from jinja.loaders import LoaderWrapper
-from jinja.datastructure import Undefined, Context, Markup
+from jinja.datastructure import Undefined, Context, Markup, FakeTranslator
 from jinja.utils import escape
 from jinja.exceptions import FilterNotFound, TestNotFound, SecurityException
 from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
@@ -102,6 +102,16 @@ class Environment(object):
             except UnicodeError:
                 return str(value).decode(self.charset, 'ignore')
 
+    def get_translator(self, context):
+        """
+        Return the translator for i18n.
+
+        A translator is an object that provides the two functions
+        ``gettext(string)`` and ``ngettext(singular, plural, n)``. Note
+        that both of them have to return unicode!
+        """
+        return FakeTranslator()
+
     def apply_filters(self, value, context, filters):
         """
         Apply a list of filters on the variable.
@@ -150,7 +160,7 @@ class Environment(object):
                 node = getattr(node, name)
         return node
 
-    def call_function(self, f, args, kwargs, dyn_args, dyn_kwargs):
+    def call_function(self, f, context, args, kwargs, dyn_args, dyn_kwargs):
         """
         Function call helper. Called for all functions that are passed
         any arguments.
@@ -162,15 +172,20 @@ class Environment(object):
         if getattr(f, 'jinja_unsafe_call', False) or \
            getattr(f, 'alters_data', False):
             raise SecurityException('unsafe function %r called' % f.__name__)
+        if getattr(f, 'jinja_context_callable', False):
+            args = (self, context) + args
         return f(*args, **kwargs)
 
-    def call_function_simple(self, f):
+    def call_function_simple(self, f, context):
         """
-        Function call without arguments.
+        Function call without arguments. Because of the smaller signature and
+        fewer logic here we have a bit of redundant code.
         """
         if getattr(f, 'jinja_unsafe_call', False) or \
            getattr(f, 'alters_data', False):
             raise SecurityException('unsafe function %r called' % f.__name__)
+        if getattr(f, 'jinja_context_callable', False):
+            return f(self, context)
         return f()
 
     def finish_var(self, value):
index 4caddd95ffc3c93ab52eff78283f0b007889fa90..6e2ef018bd9a62a53de5bdeb0c4a2ec47ef10491 100644 (file)
@@ -314,7 +314,7 @@ class PythonTranslator(Translator):
         # add translation helpers if required
         if self.require_translations:
             lines.append(
-                '    translator = context.get_translator()\n'
+                '    translator = environment.get_translator(context)\n'
                 '    def translate(s, p=None, n=None, r=None):\n'
                 '        if p is None:\n'
                 '            return translator.gettext(s) % (r or {})\n'
@@ -716,8 +716,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)' % self.handle_node(node.node)
-        return 'call_function(%s, %s, {%s}, %s, %s)' % (
+            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),
             ', '.join(['%r: %s' % i for i in kwargs.iteritems()]),
index 262db19e4718f13140b1ac5a7518d61a59fb3e83..d0f57065bc72c6c96f5c1e4467d3744d2d1b3b04 100644 (file)
@@ -23,6 +23,9 @@ try:
 except ImportError:
     deque = None
 
+#: number of maximal range items
+MAX_RANGE = 1000000
+
 _debug_info_re = re.compile(r'^\s*\# DEBUG\(filename=(.*?), lineno=(.*?)\)$')
 
 _escape_pairs = {
@@ -118,14 +121,30 @@ def find_translations(environment, source):
         queue.extend(node.getChildNodes())
 
 
-def debug_context():
+def debug_context(env, context):
     """
     Use this function in templates to get a printed context.
-    Use this only in templates because it touches the stack.
     """
-    context = sys._getframe(2).f_locals['context']
     from pprint import pformat
     return pformat(context.to_dict())
+debug_context.jinja_context_callable = True
+
+
+def safe_range(start, stop=None, step=None):
+    """
+    "Safe" form of range that does not generate too large lists.
+    """
+    # this also works with None since None is always smaller than
+    # any other value.
+    if start > MAX_RANGE:
+        start = MAX_RANGE
+    if stop > MAX_RANGE:
+        stop = MAX_RANGE
+    if step is None:
+        step = 1
+    if stop is None:
+        return range(0, start, step)
+    return range(start, stop, step)
 
 
 # python2.4 and lower has a bug regarding joining of broken generators
index 277b46ef26ef61ae0cc3946a05640c12453d3049..379c717ab2aa627338898f931b6570ba7dd689e3 100644 (file)
@@ -26,7 +26,7 @@
     <a href="http://peak.telecommunity.com/DevCenter/EasyInstall">easy_install</a>,
     you can do it using this command:
   </p>
-  <pre>easy_install Pygments</pre>
+  <pre>easy_install Jinja</pre>
   <p>
     You can also get the development source from subversion using this command:
   </p>
index c4fb8b8e87d39946f67e63726c40b7e57025fe85..abe55ab6e1b7cb035229c347ca14db2d08ccc3e9 100644 (file)
   <span class="nt">&lt;/ul&gt;</span>
 <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
 </pre></div>
-  <h2>Philosphy</h2>
+  <h2>Philosophy</h2>
   <p>
-    Application logic is for the controller but don't try to make the live for the
-    template designer too hard by giving him too less functionality.
+    Application logic is for the controller but don't try to make the life for the
+    template designer too hard by giving him too few functionality.
   </p>
   <h2>Features</h2>
   <ul>