evan's original examples...
---
<div tal:define="foo string:*foo*;
seq python:('arg', 3.4);
map python:{'*foo*': 'yes', 'keys': 'skeleton'}">
<div tal:content="seq/item:0"></div>
<div tal:content="seq/item:1/fmt:%.2f"></div>
<div tal:content="template/attr:title"></div>
<div tal:content="here/key:test3/title"></div>
<div tal:content="structure foo/fmt:structured_text"></div>
<div tal:content="map/key:keys"></div>
<div tal:content="map/var:foo"></div>
</div>
These first examples are pretty trivial, and can mostly be done today
as python expressions with no difficulty.
For the fmt: and var: examples, i'll just hand-wave, I think what I mean
is pretty clear...
<div tal:define="python:foo '*foo*';
seq python:('arg', 3.4);
map python:{'*foo*': 'yes', 'keys': 'skeleton'}">
<div tal:content="python:seq[0]"></div>
<div tal:content="python:'%.2f' % seq[0]"></div>
<div tal:content="python:template.title"></div>
<div tal:content="python:here['test3'].title"></div>
<div tal:content="structure python:zope.fmt.structured_text(foo)"></div>
<div tal:content="python:map['keys']"></div>
<div tal:content="python:zope.var(map, 'foo')"></div>
</div>
Now for Evan's tricky examples:
options/foo/item:0 | request/foo/item:0 | default
data/stat/fmt:thousands_commas | string:No data.
My solution using Python expressions:
First of all, provide a namespace that provides access to
API stuff. We can argue later about how this can be named & organized.
This namespace is always available in ZPT, and can be imported
in Python Scripts.
Next, we provide a function that behaves a little bit like the
existing path() function -
given some names, for each successive pair it tries to find
foo[bar] and getattr(foo, bar) and if both fail, we don't raise the
AttributeError or KeyError - we just return false (more on this below).
This allows you to chain function calls with "or". TALES has to be
modified a bit for this to work as I intend, as described below.
This path lookup function should just take, as arguments, any number
of ids to look up. You can pass a sequence using the *args
notation. Note that since it just tries __getitem__ and __getattr__
at each step, it works fine with mappings (e.g. the request object).
I can't think of a good name so i'm calling it zget.
Examples:
original:
options/foo/item:0 | request/foo/item:0 | default
mine:
zget('options','foo')[0] or zget('request','foo')[0] or default
original:
data/stat/fmt:thousands_commas | string:No data.
mine:
zope.fmt.thousands_commas(zget('data','stat')) or 'No data'
Note a significant difference in this latter example: in Evan's version,
the thousands_commas call can be short-circuited if data/stat
does not exist, while in mine it cannot be avoided (it is
merely called on an empty string).
How this works: the "false" value returned by zget is a
bit special - it's a lightweight "null object".
e.g. __len__ returns 0, while __getitem__, __getattr__, and __call__
return None ... and __str__ (and __repr__?) returns ''. It would also
keep an _exception attribute which would be the exception that
led to its creation (e.g. the AttributeError).
TALES would then have to be modified such that if the end result of
a TALES expression is one of these special null objects,
we re-raise its _exception. That way you still find out what the
missing item was.