changeset 241:4d81439bc097 trunk

* Added basic documentation for the text-based template language. * Directives in text templates are now closed with a simple `#end` line instead of the longer `#end<name>`.
author cmlenz
date Wed, 13 Sep 2006 14:52:58 +0000
parents 37039af315da
children 71062601458a
files doc/Makefile doc/index.txt doc/text-templates.txt doc/xml-templates.txt genshi/template.py genshi/tests/template.py
diffstat 6 files changed, 332 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -2,6 +2,7 @@
 	builder.html \
 	index.html \
 	streams.html \
+	text-templates.html \
 	xml-templates.html \
 	xpath.html
 
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -23,4 +23,5 @@
 * `Markup Streams <streams.html>`_
 * `Generating Markup Programmatically <builder.html>`_
 * `Genshi XML Template Language <xml-templates.html>`_
+* `Genshi Text Template Language <text-templates.html>`_
 * `Using XPath in Genshi <xpath.html>`_
new file mode 100644
--- /dev/null
+++ b/doc/text-templates.txt
@@ -0,0 +1,292 @@
+.. -*- mode: rst; encoding: utf-8 -*-
+
+=============================
+Genshi Text Template Language
+=============================
+
+In addition to the XML-based template language, Genshi provides a simple
+text-based template language, intended for basic plain text generation needs.
+The language is similar to Cheetah_ or Velocity_.
+
+.. _cheetah: http://cheetahtemplate.org/
+.. _velocity: http://jakarta.apache.org/velocity/
+
+This document describes the template language and will be most useful as
+reference to those developing Genshi text templates. Templates are XML files of some
+kind (such as XHTML) that include processing directives_ (elements or
+attributes identified by a separate namespace) that affect how the template is
+rendered, and template expressions_ that are dynamically substituted by
+variable data.
+
+
+.. contents:: Contents
+   :depth: 3
+.. sectnum::
+
+----------
+Python API
+----------
+
+The Python code required for templating with Genshi is generally based on the
+following pattern:
+
+* Attain a ``Template`` object from a string or file object containing the
+  template source. This can either be done directly, or through a
+  ``TemplateLoader`` instance.
+* Call the ``generate()`` method of the template, passing any data that should
+  be made available to the template as keyword arguments.
+* Serialize the resulting stream using its ``render()`` method.
+
+For example::
+
+  from genshi.template import TextTemplate
+
+  tmpl = TextTemplate('$title')
+  stream = tmpl.generate(title='Hello, world!')
+  print stream.render('text')
+
+That code would produce the following output::
+
+  Hello, world!
+
+Using a template loader provides the advantage that “compiled” templates are
+automatically cached, and only parsed again when the template file changes::
+
+  from genshi.template import TemplateLoader
+
+  loader = TemplateLoader([templates_dir])
+  tmpl = loader.load('test.txt' cls=TextTemplate)
+  stream = tmpl.generate(title='Hello, world!')
+  print stream.render('text')
+
+
+.. _`expressions`:
+
+--------------------
+Template Expressions
+--------------------
+
+Python_ expressions can be used in text and attribute values. An expression is
+substituted with the result of its evaluation against the template data.
+Expressions need to prefixed with a dollar sign (``$``) and usually enclosed in
+curly braces (``{…}``).
+
+.. _python: http://www.python.org/
+
+If the expression starts with a letter and contains only letters and digits,
+the curly braces may be omitted. In all other cases, the braces are required
+so that the template processors knows where the expression ends::
+
+  >>> from genshi.template import TextTemplate
+  >>> tmpl = TextTemplate('${items[0].capitalize()} item')
+  >>> print tmpl.generate(items=['first', 'second'])
+  First item
+
+Expressions support the full power of Python. In addition, it is possible to
+access items in a dictionary using “dotted notation” (i.e. as if they were
+attributes), and vice-versa (i.e. access attributes as if they were items in
+a dictionary)::
+
+  >>> from genshi.template import TextTemplate
+  >>> tmpl = TextTemplate('${dict.foo}')
+  >>> print tmpl.generate(dict={'foo': 'bar'})
+  bar
+
+
+.. _`directives`:
+
+-------------------
+Template Directives
+-------------------
+
+Directives are lines starting with a ``#`` character followed by the directive
+name. They can affect how the template is rendered in a number of ways: Genshi
+provides directives for conditionals and looping, among others.
+
+For example::
+
+  #if foo
+    Bar
+  #end
+
+Directives must be on separate lines, and the ``#`` character must be be the
+first non-whitespace character on that line. Each directive must be “closed”
+using a ``#end`` marker.
+
+
+Conditional Sections
+====================
+
+.. _`#if`:
+
+``#if``
+---------
+
+The content is only rendered if the expression evaluates to a truth value::
+
+  #if foo
+    ${bar}
+  #end
+
+Given the data ``foo=True`` and ``bar='Hello'`` in the template context, this
+would produce::
+
+    Hello
+
+
+.. _`#choose`:
+.. _`#when`:
+.. _`#otherwise`:
+
+``#choose``
+-------------
+
+The ``#choose`` directive, in combination with the directives ``#when`` and
+``#otherwise`` provides advanced contional processing for rendering one of
+several alternatives. The first matching ``#when`` branch is rendered, or, if
+no ``#when`` branch matches, the ``#otherwise`` branch is be rendered.
+
+If the ``#choose`` directive has no argument the nested ``#when`` directives
+will be tested for truth::
+
+  The answer is:
+  #choose
+    #when 0 == 1
+      0
+    #end
+    #when 1 == 1
+      1
+    #end
+    #otherwise
+      2
+    #end
+  #end
+
+This would produce the following output::
+
+  The answer is:
+      1
+
+If the ``#choose`` does have an argument, the nested ``#when`` directives will
+be tested for equality to the parent ``#choose`` value::
+
+  The answer is:
+  #choose 1
+    #when 0
+      0
+    #end
+    #when 1
+      1
+    #end
+    #otherwise
+      2
+    #end
+  #end
+
+This would produce the following output::
+
+  The answer is:
+      1
+
+
+Looping
+=======
+
+.. _`#for`:
+
+``#for``
+----------
+
+The content is repeated for every item in an iterable::
+
+  Your items:
+  #for item in items
+    * ${item}
+  #end
+
+Given ``items=[1, 2, 3]`` in the context data, this would produce::
+
+  Your items
+    * 1
+    * 2
+    * 3
+
+
+Snippet Reuse
+=============
+
+.. _`#def`:
+.. _`macros`:
+
+``#def``
+----------
+
+The ``#def`` directive can be used to create macros, i.e. snippets of template
+text that have a name and optionally some parameters, and that can be inserted
+in other places::
+
+  #def greeting(name)
+    Hello, ${name}!
+  #end
+  ${greeting('world')}
+  ${greeting('everyone else')}
+
+The above would be rendered to::
+
+    Hello, world!
+    Hello, everyone else!
+
+If a macro doesn't require parameters, it can be defined as well as called
+without the parenthesis. For example::
+
+  #def greeting
+    Hello, world!
+  #end
+  ${greeting}
+
+The above would be rendered to::
+
+    Hello, world!
+
+
+Variable Binding
+================
+
+.. _`#with`:
+
+``#with``
+-----------
+
+The ``#with`` directive lets you assign expressions to variables, which can
+be used to make expressions inside the directive less verbose and more
+efficient. For example, if you need use the expression ``author.posts`` more
+than once, and that actually results in a database query, assigning the results
+to a variable using this directive would probably help.
+
+For example::
+
+  Magic numbers!
+  #with y=7; z=x+10
+    $x $y $z
+  #end
+
+Given ``x=42`` in the context data, this would produce::
+
+  Magic numbers!
+    42 7 52
+
+Note that if a variable of the same name already existed outside of the scope
+of the ``#with`` directive, it will **not** be overwritten. Instead, it will
+have the same value it had prior to the ``#with`` assignment. Effectively,
+this means that variables are immutable in Genshi.
+
+
+.. _comments:
+
+--------
+Comments
+--------
+
+Lines where the first non-whitespace characters are ``##`` are removed from
+the output, and can thus be used for comments. This can be escaped using a
+backslash.
--- a/doc/xml-templates.txt
+++ b/doc/xml-templates.txt
@@ -4,9 +4,9 @@
 Genshi XML Template Language
 ============================
 
-Genshi provides a simple XML-based template language that is heavily inspired
-by Kid_, which in turn was inspired by a number of existing template languages,
-namely XSLT_, TAL_, and PHP_.
+Genshi provides a XML-based template language that is heavily inspired by Kid_,
+which in turn was inspired by a number of existing template languages, namely
+XSLT_, TAL_, and PHP_.
 
 .. _kid: http://kid-templating.org/
 .. _python: http://www.python.org/
@@ -15,8 +15,8 @@
 .. _php: http://www.php.net/
 
 This document describes the template language and will be most useful as
-reference to those developing Genshi templates. Templates are XML files of some
-kind (such as XHTML) that include processing directives_ (elements or
+reference to those developing Genshi XML templates. Templates are XML files of
+some kind (such as XHTML) that include processing directives_ (elements or
 attributes identified by a separate namespace) that affect how the template is
 rendered, and template expressions_ that are dynamically substituted by
 variable data.
@@ -33,8 +33,8 @@
 The Python code required for templating with Genshi is generally based on the
 following pattern:
 
-* Attain a ``Template`` object from a string or file object containing the
-  template XML source. This can either be done directly, or through a
+* Attain a ``MarkupTemplate`` object from a string or file object containing
+  the template XML source. This can either be done directly, or through a
   ``TemplateLoader`` instance.
 * Call the ``generate()`` method of the template, passing any data that should
   be made available to the template as keyword arguments.
@@ -42,9 +42,9 @@
 
 For example::
 
-  from genshi.template import Template
+  from genshi.template import MarkupTemplate
 
-  tmpl = Template('<h1>$title</h1>')
+  tmpl = MarkupTemplate('<h1>$title</h1>')
   stream = tmpl.generate(title='Hello, world!')
   print stream.render('xhtml')
 
@@ -78,9 +78,9 @@
 the curly braces may be omitted. In all other cases, the braces are required so
 that the template processors knows where the expression ends::
 
-  >>> from genshi.template import Context, Template
-  >>> tmpl = Template('<em>${items[0].capitalize()} item</em>')
-  >>> print tmpl.generate(Context(items=['first', 'second']))
+  >>> from genshi.template import MarkupTemplate
+  >>> tmpl = MarkupTemplate('<em>${items[0].capitalize()} item</em>')
+  >>> print tmpl.generate(items=['first', 'second'])
   <em>First item</em>
 
 Expressions support the full power of Python. In addition, it is possible to
@@ -88,9 +88,9 @@
 attributes), and vice-versa (i.e. access attributes as if they were items in a
 dictionary)::
 
-  >>> from genshi.template import Context, Template
-  >>> tmpl = Template('<em>${dict.foo}</em>')
-  >>> print tmpl.generate(Context(dict={'foo': 'bar'}))
+  >>> from genshi.template import MarkupTemplate
+  >>> tmpl = MarkupTemplate('<em>${dict.foo}</em>')
+  >>> print tmpl.generate(dict={'foo': 'bar'})
   <em>bar</em>
 
 
--- a/genshi/template.py
+++ b/genshi/template.py
@@ -742,27 +742,16 @@
 
 
 class Template(object):
-    """Can parse a template and transform it into the corresponding output
-    based on context data.
+    """Abstract template base class.
+    
+    This class implements most of the template processing model, but does not
+    specify the syntax of templates.
     """
     __metaclass__ = TemplateMeta
 
     EXPR = StreamEventKind('EXPR') # an expression
     SUB = StreamEventKind('SUB') # a "subprogram"
 
-    directives = [('def', DefDirective),
-                  ('match', MatchDirective),
-                  ('when', WhenDirective),
-                  ('otherwise', OtherwiseDirective),
-                  ('for', ForDirective),
-                  ('if', IfDirective),
-                  ('choose', ChooseDirective),
-                  ('with', WithDirective),
-                  ('replace', ReplaceDirective),
-                  ('content', ContentDirective),
-                  ('attrs', AttrsDirective),
-                  ('strip', StripDirective)]
-
     def __init__(self, source, basedir=None, filename=None, loader=None):
         """Initialize a template from either a string or a file-like object."""
         if isinstance(source, basestring):
@@ -1006,8 +995,15 @@
 
 
 class MarkupTemplate(Template):
-    """Can parse a template and transform it into the corresponding output
-    based on context data.
+    """Implementation of the template language for XML-based templates.
+    
+    >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
+    ...   <li py:for="item in items">${item}</li>
+    ... </ul>''')
+    >>> print tmpl.generate(items=[1, 2, 3])
+    <ul>
+      <li>1</li><li>2</li><li>3</li>
+    </ul>
     """
     NAMESPACE = Namespace('http://genshi.edgewall.org/')
 
@@ -1035,14 +1031,7 @@
             self.filters.append(IncludeFilter(loader))
 
     def _parse(self):
-        """Parse the template.
-        
-        The parsing stage parses the XML template and constructs a list of
-        directives that will be executed in the render stage. The input is
-        split up into literal output (markup that does not depend on the
-        context data) and actual directives (commands or variable
-        substitution).
-        """
+        """Parse the template from an XML document."""
         stream = [] # list of events of the "compiled" template
         dirmap = {} # temporary mapping of directives to elements
         ns_prefix = {}
@@ -1211,7 +1200,7 @@
     ... We have the following items for you:
     ... #for item in items
     ...  * $item
-    ... #endfor
+    ... #end
     ... 
     ... All the best,
     ... Foobar''')
@@ -1238,6 +1227,7 @@
     _directive_re = re.compile('^\s*#(\w+.*)\n?', re.MULTILINE)
 
     def _parse(self):
+        """Parse the template from text input."""
         stream = [] # list of events of the "compiled" template
         dirmap = {} # temporary mapping of directives to elements
         depth = 0
@@ -1254,7 +1244,7 @@
                     stream.append((kind, data, pos))
                 lineno += len(text.splitlines())
 
-            text = source[start:end].lstrip().lstrip('#')
+            text = source[start:end].lstrip()[1:]
             lineno += len(text.splitlines())
             directive = text.split(None, 1)
             if len(directive) > 1:
@@ -1262,7 +1252,7 @@
             else:
                 command, value = directive[0], None
 
-            if not command.startswith('end'):
+            if command != 'end':
                 cls = self._dir_by_name.get(command)
                 if cls is None:
                     raise BadDirectiveError(command)
@@ -1271,7 +1261,6 @@
                 depth += 1
             else:
                 depth -= 1
-                command = command[3:]
                 if depth in dirmap:
                     directive, start_offset = dirmap.pop(depth)
                     substream = stream[start_offset:]
--- a/genshi/tests/template.py
+++ b/genshi/tests/template.py
@@ -233,14 +233,14 @@
         tmpl = TextTemplate("""#choose
           #when 1 == 1
             1
-          #endwhen
+          #end
           #when 2 == 2
             2
-          #endwhen
+          #end
           #when 3 == 3
             3
-          #endwhen
-        #endchoose""")
+          #end
+        #end""")
         self.assertEqual("""            1\n""", str(tmpl.generate()))
 
 
@@ -367,7 +367,7 @@
         tmpl = TextTemplate("""
           #def echo(greeting, name='world')
             ${greeting}, ${name}!
-          #enddef
+          #end
           ${echo('Hi', name='you')}
         """)
         self.assertEqual("""                      Hi, you!
Copyright (C) 2012-2017 Edgewall Software