more docs and fixed bug in parser that assigned lineno for ExprStmt wrong

--HG--
branch : trunk
diff --git a/docs/conf.py b/docs/conf.py
index 4c984d2..237b61b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -67,7 +67,7 @@
 #show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'autumn'
+pygments_style = 'jinjaext.JinjaStyle'
 
 
 # Options for HTML output
@@ -132,7 +132,14 @@
 ]
 
 # Additional stuff for the LaTeX preamble.
-latex_preamble = ''
+latex_preamble = '''
+\usepackage{palatino}
+\definecolor{TitleColor}{rgb}{0.7,0,0}
+\definecolor{InnerLinkColor}{rgb}{0.7,0,0}
+\definecolor{OuterLinkColor}{rgb}{0.8,0,0}
+\definecolor{VerbatimColor}{rgb}{0.98,0.98,0.98}
+\definecolor{VerbatimBorderColor}{rgb}{0.8,0.8,0.8}
+'''
 
 # Documents to append as an appendix to all manuals.
 #latex_appendices = []
diff --git a/docs/jinjaext.py b/docs/jinjaext.py
index c0dfe44..bd4cab6 100644
--- a/docs/jinjaext.py
+++ b/docs/jinjaext.py
@@ -8,59 +8,115 @@
     :copyright: Copyright 2008 by Armin Ronacher.
     :license: BSD.
 """
+import re
 import inspect
+from types import BuiltinFunctionType
 from docutils import nodes
 from docutils.statemachine import ViewList
 from sphinx.ext.autodoc import prepare_docstring
 
 
-def format_filter(name, aliases, func):
-    try:
-        argspec = inspect.getargspec(func)
-    except:
+from pygments.style import Style
+from pygments.token import Keyword, Name, Comment, String, Error, \
+     Number, Operator, Generic
+
+
+class JinjaStyle(Style):
+    title = 'Jinja Style'
+    default_style = ""
+    styles = {
+        Comment:                    'italic #aaaaaa',
+        Comment.Preproc:            'noitalic #B11414',
+        Comment.Special:            'italic #505050',
+
+        Keyword:                    'bold #B80000',
+        Keyword.Type:               '#808080',
+
+        Operator.Word:              '#333333',
+
+        Name.Builtin:               '#333333',
+        Name.Function:              '#333333',
+        Name.Class:                 'bold #333333',
+        Name.Namespace:             'bold #333333',
+        Name.Entity:                'bold #363636',
+        Name.Attribute:             '#686868',
+        Name.Tag:                   'bold #686868',
+        Name.Decorator:             '#686868',
+
+        String:                     '#BE9B5D',
+        Number:                     '#FF0000',
+
+        Generic.Heading:            'bold #000080',
+        Generic.Subheading:         'bold #800080',
+        Generic.Deleted:            '#aa0000',
+        Generic.Inserted:           '#00aa00',
+        Generic.Error:              '#aa0000',
+        Generic.Emph:               'italic',
+        Generic.Strong:             'bold',
+        Generic.Prompt:             '#555555',
+        Generic.Output:             '#888888',
+        Generic.Traceback:          '#aa0000',
+
+        Error:                      '#F00 bg:#FAA'
+    }
+
+_sig_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*(\(.*?\))')
+
+
+def format_function(name, aliases, func):
+    lines = inspect.getdoc(func).splitlines()
+    signature = '()'
+    if isinstance(func, BuiltinFunctionType):
+        match = _sig_re.match(lines[0])
+        if match is not None:
+            del lines[:1 + bool(lines and not lines[0])]
+            signature = match.group(1)
+    else:
         try:
-            argspec = inspect.getargspec(func.__init__)
+            argspec = inspect.getargspec(func)
+            if getattr(func, 'environmentfilter', False) or \
+               getattr(func, 'contextfilter', False):
+                del argspec[0][0]
+            signature = inspect.formatargspec(*argspec)
         except:
-            try:
-                argspec = inspect.getargspec(func.__new__)
-            except:
-                return []
-        del argspec[0][0]
-    if getattr(func, 'environmentfilter', False) or \
-       getattr(func, 'contextfilter', False):
-        del argspec[0][0]
-    signature = inspect.formatargspec(*argspec)
+            pass
     result = ['.. function:: %s%s' % (name, signature), '']
-    for line in inspect.getdoc(func).splitlines():
-        result.append('    ' + line)
+    result.extend('    ' + line for line in lines)
     if aliases:
         result.extend(('', '    :aliases: %s' % ', '.join(
                       '``%s``' % x for x in sorted(aliases))))
     return result
 
 
-def jinja_filters(dirname, arguments, options, content, lineno,
-                  content_offset, block_text, state, state_machine):
-    from jinja2.defaults import DEFAULT_FILTERS
-    mapping = {}
-    for name, func in DEFAULT_FILTERS.iteritems():
-        mapping.setdefault(func, []).append(name)
-    filters = []
-    for func, names in mapping.iteritems():
-        aliases = sorted(names, key=lambda x: len(x))
-        name = aliases.pop()
-        filters.append((name, aliases, func))
-    filters.sort()
+def dump_functions(mapping):
+    def directive(dirname, arguments, options, content, lineno,
+                      content_offset, block_text, state, state_machine):
+        reverse_mapping = {}
+        for name, func in mapping.iteritems():
+            reverse_mapping.setdefault(func, []).append(name)
+        filters = []
+        for func, names in reverse_mapping.iteritems():
+            aliases = sorted(names, key=lambda x: len(x))
+            name = aliases.pop()
+            filters.append((name, aliases, func))
+        filters.sort()
 
-    result = ViewList()
-    for name, aliases, func in filters:
-        for item in format_filter(name, aliases, func):
-            result.append(item, '<jinjaext>')
+        result = ViewList()
+        for name, aliases, func in filters:
+            for item in format_function(name, aliases, func):
+                result.append(item, '<jinjaext>')
 
-    node = nodes.paragraph()
-    state.nested_parse(result, content_offset, node)
-    return node.children
+        node = nodes.paragraph()
+        state.nested_parse(result, content_offset, node)
+        return node.children
+    return directive
+
+
+from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS
+jinja_filters = dump_functions(DEFAULT_FILTERS)
+jinja_tests = dump_functions(DEFAULT_TESTS)
 
 
 def setup(app):
-    app.add_directive('jinjafilters', jinja_filters, 1, (0, 0, 0))
+    app.add_directive('jinjafilters', jinja_filters, 0, (0, 0, 0))
+    app.add_directive('jinjatests', jinja_tests, 0, (0, 0, 0))
diff --git a/docs/templates.rst b/docs/templates.rst
index a3b2325..8171f79 100644
--- a/docs/templates.rst
+++ b/docs/templates.rst
@@ -89,7 +89,7 @@
 around the arguments, like a function call.  This example will join a list
 by spaces:  ``{{ list|join(', ') }}``.
 
-The `builtin-filters`_ below describes all the builtin filters.
+The :ref:`builtin-filters` below describes all the builtin filters.
 
 
 Tests
@@ -110,7 +110,7 @@
     {% if loop.index is divisibleby 3 %}
     {% if loop.index is divisibleby(3) %}
 
-The `builtin-tests`_ below descibes all the builtin tests.
+The :ref:`builtin-tests` below descibes all the builtin tests.
 
 
 Comments
@@ -267,7 +267,7 @@
     escaped HTML.
 
 Working with Manual Escaping
-----------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 If manual escaping is enabled it's **your** responsibility to escape
 variables if needed.  What to escape?  If you have a variable that *may*
@@ -277,28 +277,111 @@
 ``{{ user.username|e }}``.
 
 Working with Automatic Escaping
--------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 When automatic escaping is enabled everything is escaped by default except
 for values explicitly marked as safe.  Those can either be marked by the
 application or in the template by using the `|safe` filter.  The main
-problem with this approach is that python itself doesn't have the concept
+problem with this approach is that Python itself doesn't have the concept
 of tainted values so the information if a value is safe or unsafe can get
 lost.  If the information is lost escaping will take place which means that
 you could end up with double escaped contents.
 
 Double escaping is easy to avoid however, just relay on the tools Jinja2
-provides and don't use builtin python constructs such as the string modulo
+provides and don't use builtin Python constructs such as the string modulo
 operator.
 
 Functions returning template data (macros, `super`, `self.BLOCKNAME`) return
 safe markup always.
 
 String literals in templates with automatic escaping are considered unsafe
-too.  The reason for this is that the safe string is an extension to python
+too.  The reason for this is that the safe string is an extension to Python
 and not every library will work properly with it.
 
 
+List of Control Structures
+--------------------------
+
+A control structure refers to all those things that control the flow of a
+program - conditionals (i.e. if/elif/else), for-loops, as well as things like
+macros and blocks.  Control structures appear inside ``{% ... %}`` blocks
+in the default syntax.
+
+For Loops
+~~~~~~~~~
+
+Loop over each item in a sequece.  For example, to display a list of users
+provided in a variable called `users`:
+
+.. sourcecode:: html+jinja
+
+    <h1>Members</h1>
+    <ul>
+    {% for user in users %}
+      <li>{{ user.username|e }}</li>
+    {% endfor %}
+    </ul>
+
+Inside of a for loop block you can access some special variables:
+
++-----------------------+---------------------------------------------------+
+| `loop.index`          | The current iteration of the loop. (1 indexed)    |
++-----------------------+---------------------------------------------------+
+| `loop.index0`         | The current iteration of the loop. (0 indexed)    |
++-----------------------+---------------------------------------------------+
+| `loop.revindex`       | The number of iterations from the end of the loop |
+|                       | (1 indexed)                                       |
++-----------------------+---------------------------------------------------+
+| `loop.revindex0`      | The number of iterations from the end of the loop |
+|                       | (0 indexed)                                       |
++-----------------------+---------------------------------------------------+
+| `loop.first`          | True if first iteration.                          |
++-----------------------+---------------------------------------------------+
+| `loop.last`           | True if last iteration.                           |
++-----------------------+---------------------------------------------------+
+| `loop.length`         | The number of items in the sequence.              |
++-----------------------+---------------------------------------------------+
+| `loop.cycle`          | A helper function to cycle between a list of      |
+|                       | sequences.  See the explanation below.            |
++-----------------------+---------------------------------------------------+
+
+Within a for-loop, it's psosible to cycle among a list of strings/variables
+each time through the loop by using the special `loop.cycle` helper:
+
+.. sourcecode:: html+jinja
+
+    {% for row in rows %}
+        <li class="{{ loop.cycle('odd', 'even') }}">{{ row }}</li>
+    {% endfor %}
+
+Unlike in Python it's not possible to `break` or `continue` in a loop.  You
+can however filter the sequence during iteration which allows you to skip
+items.  The following example skips all the users which are hidden:
+
+.. sourcecode:: html+jinja
+
+    {% for user in users if not user.hidden %}
+        <li>{{ user.username|e }}</li>
+    {% endfor %}
+
+The advantage is that the special `loop` variable will count correctly thus
+not counting the users not iterated over.
+
+If no iteration took place because the sequence was empty or the filtering
+removed all the items from the sequence you can render a replacement block
+by using `else`:
+
+.. sourcecode:: html+jinja
+
+    <ul>
+    {% for user in users %}
+        <li>{{ user.username|e }}</li>
+    {% else %}
+        <li><em>no users found</em></li>
+    {% endif %}
+    </ul>
+    
+
 .. _builtin-filters:
 
 List of Builtin Filters
@@ -312,4 +395,4 @@
 List of Builtin Tests
 ---------------------
 
-bleh
+.. jinjatests::
diff --git a/jinja2/filters.py b/jinja2/filters.py
index e8cf5aa..4c9766c 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -90,7 +90,7 @@
 
 
 @environmentfilter
-def do_xmlattr(_environment, *args, **kwargs):
+def do_xmlattr(_environment, d, autospace=True):
     """Create an SGML/XML attribute string based on the items in a dict.
     All values that are neither `none` nor `undefined` are automatically
     escaped:
@@ -111,14 +111,14 @@
         </ul>
 
     As you can see it automatically prepends a space in front of the item
-    if the filter returned something.
+    if the filter returned something unless the second parameter is `False`.
     """
     rv = u' '.join(
         u'%s="%s"' % (escape(key), escape(value))
         for key, value in dict(*args, **kwargs).iteritems()
         if value is not None and not isinstance(value, Undefined)
     )
-    if rv:
+    if autospace and rv:
         rv = u' ' + rv
     if _environment.autoescape:
         rv = Markup(rv)
@@ -310,10 +310,7 @@
 
 
 def do_indent(s, width=4, indentfirst=False):
-    """
-    {{ s|indent[ width[ indentfirst[ usetab]]] }}
-
-    Return a copy of the passed string, each line indented by
+    """Return a copy of the passed string, each line indented by
     4 spaces. The first line is not indented. If you want to
     change the number of spaces or indent the first line too
     you can pass additional parameters to the filter:
@@ -330,8 +327,7 @@
 
 
 def do_truncate(s, length=255, killwords=False, end='...'):
-    """
-    Return a truncated copy of the string. The length is specified
+    """Return a truncated copy of the string. The length is specified
     with the first parameter which defaults to ``255``. If the second
     parameter is ``true`` the filter will cut the text at length. Otherwise
     it will try to save the last word. If the text was in fact
@@ -597,6 +593,37 @@
         return tuple.__new__(cls, (key, list(value)))
 
 
+def do_list(value):
+    """Convert the value into a list.  If it was a string the returned list
+    will be a list of characters.
+    """
+    return list(value)
+
+
+def do_mark_safe(value):
+    """Mark the value as safe which means that in an environment with automatic
+    escaping enabled this variable will not be escaped.
+    """
+    return Markup(value)
+
+
+def do_reverse(value):
+    """Reverse the object or return an iterator the iterates over it the other
+    way round.
+    """
+    if isinstance(value, basestring):
+        return value[::-1]
+    try:
+        return reversed(value)
+    except TypeError:
+        try:
+            rv = list(value)
+            rv.reverse()
+            return rv
+        except TypeError:
+            raise FilterArgumentError('argument must be iterable')
+
+
 FILTERS = {
     'replace':              do_replace,
     'upper':                do_upper,
@@ -612,7 +639,7 @@
     'count':                len,
     'dictsort':             do_dictsort,
     'length':               len,
-    'reverse':              reversed,
+    'reverse':              do_reverse,
     'center':               do_center,
     'indent':               do_indent,
     'title':                do_title,
@@ -628,7 +655,7 @@
     'int':                  do_int,
     'float':                do_float,
     'string':               soft_unicode,
-    'list':                 list,
+    'list':                 do_list,
     'urlize':               do_urlize,
     'format':               do_format,
     'trim':                 do_trim,
@@ -640,6 +667,6 @@
     'round':                do_round,
     'sort':                 do_sort,
     'groupby':              do_groupby,
-    'safe':                 Markup,
+    'safe':                 do_mark_safe,
     'xmlattr':              do_xmlattr
 }
diff --git a/jinja2/parser.py b/jinja2/parser.py
index 8a0eb6a..8bc1307 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -52,7 +52,7 @@
             ext = self.extensions.get(self.stream.current.value)
             if ext is not None:
                 return ext(self)
-        lineno = self.stream.current
+        lineno = self.stream.current.lineno
         expr = self.parse_tuple()
         if self.stream.current.type == 'assign':
             result = self.parse_assign(expr)