PTL - Python Template Language
==============================

[Mostly written by GPW, with minor revisions and updating by AMK]

PTL is a mini-language intended to aid in the automated creation of
text documents using embedded Python and Python-like statements.
Initially, it will be geared towards generating HTML/XML documents to
be served via HTTP, but there shouldn't be anything web-specific in
the basic syntax.

PTL is strongly inspired by DTML, but aims for a simpler syntax with
more natural integration of Python and fewer hacks for backwards
compatibility (because there *are* no old versions of PTL!).  It also
borrows some syntax ideas from the PHP/ASP/JSP world, but mainly by
osmosis as I'm pretty ignorant of those systems.

The intended audience is people who already know Python, and can deal
with terms like "object", "attribute", and "method" on Python's terms.


Basic idea
----------

A template is a chunk of text (typically, one per file) with several
types of embedded directives:
  * Python expressions
  * Python statements
  * Python blocks
  * PTL statements
  * PTL values

Python expressions will be eval'd in certain well-defined contexts;
Python statements and blocks will be exec'd in that same context; and
PTL statements and values are subject to PTL-specific syntax and
semantics that are suspiciously similar to Python syntax and semantics.
In particular, PTL statements affect the same (Python) namespace as
Python expressions and statements, and PTL values "read" that same
namespace.


Using PTL statements and values
-------------------------------

The idea behind PTL statements is to perform some logic or iteration on
the text of the document, rather than on values in Python-space.  For
example, you might generate a list of bullet points in an HTML document
with the following PTL statement:

    <? for item in some_list ?>
      <li><? item ?>
    <? /for ?>

assuming that 'some_list' is a (Python) list of strings, or of objects
with a string representation that you want to insert directly into the
document.  Here, "<?item?>" is a PTL value; it expands to the string
representation of the 'item' variable, ie. of each value of 'some_list'
in turn.

PTL values can be a little more complex than "<? item ?>".  In particular,
you can put any PTL "dotted name" in there; a dotted name is just a list
of dot-separated names, eg. "foo" or "foo.bar.baz".  A name, in turn is
a string matching /^[a-zA-Z_][a-zA-Z0-9_]*$/.

Thus, PTL values can be used to fetch attributes of a Python object.
For example, if 'some_list' were a list of Python objects, each of which
has a 'url' and 'description' attribute, you might write the following
loop:

    <? for item in some_list ?>
      <li><a href="<? item.url ?>"><? item.description ?></a>
    <? /for ?>

A PTL statement reference can be found at the bottom of this document.

Any PTL directive can also be written either as "<? ... ?>" or
"<?ptl ...  ?>" (the former for lazy typists, the latter for XML
compatibility).


Using Python expressions and statements
---------------------------------------

When you can't get there from here just using PTL statements, you can
escape into full-blown Python using Python expressions, statements, and
blocks.  Each would be eval'd (or exec'd) in the same context, which is
also the context of the names and dotted names (and expressions?) used
in PTL statements and values.

For example, if 'item' is a Python object that we would rather call
methods on than directly access attributes, the following Python
expression would do the trick:

    <? expr "item.get_url()" ?>

Or, if we need to assign a variable in a PTL template:

    <? stmt "x = 3" ?>

Of course, you have full access to Python in either Python expressions
or statements:

    <? expr "'this=%s, that=%s' % (`this`, `that`)" ?>

    <? stmt "foo.x = item1.get_this() * item2.get_that()" ?>

and so forth.

If you need a multi-line chunk of Python code, you can use the <?block>
directive:

    <?block?>
      i = 0
      for item in sequence:
        item.set_something (i)
        i = i + 1
    <?/block?>

Since this is multiple lines of Python code, whitespace matters; in
particular, the <?/block> tag must be on a new line with optional
leading whitespace.

Thus, the following are all valid:

    <? item ?>
    <? item.url ?>
    <? item.get_url() ?>
    <? while not_done ?>
    <? while not done ?>
    <? while x > 3 ?>
    <? for i in thing.get_list(256 >> 4) ?>
    <? stmt x = 37 ?>
    <? stmt foo.x = 42 ?>
    <? block ?>
       for i in sequence:
         foo[i].set_blah (37)
    <? /block ?>

Again, <?ptl ... ?> would always be valid where I have written
<? ... ?>.

PTL statement reference
-----------------------

The complete list of PTL statements is:

    <? expression ?>

      Evaluate the given Python expression, and substitute the str()
      of the resulting value.

    <? stmt statement-body ?>

      Execute the statements in statement-body.  They can't span
      multiple lines, but you can use multiple Python statement
      separated with semicolons.  Use the  <? block ?> ... <? /block ?> 
      directive for multiple lines.

    <? block ?> body <? /block ?>

      Execute multiple lines of Python code.  'body' is incorporated
      verbatim in the Python function generated for a template, with
      the correct indentation level.

    <? for item in seq ?> body <? /for ?>
      Iterate over the Python sequence 'seq'; 'item' is bound in turn to
      each element of 'seq' and 'body' is evaluated.  The "for"
      statement evaluates to the concatenation of all the sequential
      evaluations of 'body'.  'body' is an arbitrary chunk of PTL text,
      possibly including other PTL directives.

    <? while cond ?> body <? /while ?>
      Evaluate 'body' repeatedly as long as 'cond' is true.  The "while"
      statement evaluates to the concatenation of all evaluations of
      'body'.  'body' is an arbitrary chunk of PTL text that can contain
      other PTL directives.

    <? if cond1 ?>
      block1
   [<? elif cond2 ?> 
      block2
      ...
    <? elif condN ?>
      blockN
   ]
   [<? else ?>
      blockX
   ]
    <? /if ?>
      Conditionally evaluate some block of text.  If 'cond1' is true,
      evaluate block1; else if 'cond2' is true, evaluate block2, and so
      forth.  If none of 'cond1' .. 'condN' are true, evaluate blockX.
      If none of 'cond1' .. 'condN' are true, and there is no "else"
      tag, the "if" statement evaluates to the empty string.

    <? print value ?>
      Output 'value' to the PTL debug log; if debugging mode is off, 
      the output simply disappears.

Proposed statements that aren't yet implemented:

    <? raise type [, value] ?>
      raise an exception and abort evaluation of the current template
      (unless the exception is caught by a "try" statement).  'type'
      should be one of the standard Python exceptions, or one of the PTL
      exceptions (???).  (Or, you can import additional exception types,
      just as you can import any Python object with the "import"
      statement.)

      XXX as usual, how do we restrict 'value'?  This time, though, the
      question is: "Python string or expression"?  (Or maybe "PTL
      string" -- which, as usual, would be very similar to a Python
      string, but parsed by PTL.)  Should be OK to restrict 'type' to a
      dotted name, as it's just an exception class.

    <? try ?>
      block0
    <? except [, type1 [, value1]] ?>
      block1
   [<? except [, type2 [, value2]] ?>
      block2
      ...
    <? except [, typeN [, valueN]] ?>
      blockN
   ]
   [<? else ?>
      blockX
   ]      
    <? /try ?>
      Catch exceptions, whether they were raised by a <? raise ?>
      statement or from within a Python expression or statement.  If an
      exception is raised in block0 (ie. while block0 is being
      evaluated), and its type matches typei (i in 1 .. N), then blocki
      is evaluted.  If no exception is raised by block0, the <? try ?>
      statement evaluates to the value of block0 concatenated to the
      value of blockX; if an exception is raised but not caught,
      evaluation of the whole template aborts and the exception is
      passed up Python's call stack.

      'type' and 'value' are both restricted to dotted names.

    XXX I have successfully used Zope and DTML without ever using
    <dtml-raise>, <dtml-try>, etc.  It could be that exception handling
    at the PTL level is purely a frill (but there are undoubtedly times
    when it would be nice to have... which is probably why it's in
    DTML!).

    <? break ?>
      prematurely terminate a "for" or "while" loop

    <? continue ?>
      prematurely jump to the next iteration of a "for" or "while" loop

    <? import module1 [, module2, ..., moduleN]>
    <? from module import name1 [, name2, ..., nameN]>
      Import a Python module (or modules), or import a list of names
      from a module.  In the first form, the names 'module1'
      .. 'moduleN' are added to the current namespace (the context
      in which all PTL statements and values and all Python statements
      and expressions in the current document are evaluated).  In the
      second form, 'module' is loaded by the Python interpreter, and
      names 'name1' .. 'nameN' are added to the current namespace using
      their values in 'module'.

    XXX it's not clear whether we could import other templates, or
    import from other templates, or what.  And what about importing
    templates in Python modules?  (We want to be able to call a template 
    from Python code, but we have to be very careful to distinguish the
    module-ness -- importability -- from the function-ness --
    callability -- of a template.)
