This is a rough draft of the documentation that will describe the various keyword commands that are available in TT3. It is incomplete and incorrect in places. Please don't trust anything you read.
Defines a content block. Can be used in "anonymous block" form:
block; ...; end;
You can assign a block to a variable to capture the content.
content = block; ...; end;
You can also create named blocks
block foo; ...; end; fill foo;
NOTE: named blocks aren't implemented yet
Defines a content block like 'block', but it has the same runtime semantics as Perl's 'do' block. That is, it returns *only* the result of the last expression in the block;
a = do { b c d }; # same as: a = d
is
is like =
but expects a block on the RHS instead of an expression.
In pseudo-grammar terms:
=is
Thus is
is the same thing as:
= block;; end
Here's an example:
a is; # same as: a = block; ...; end ...; end;
as
is similar to is
but with its arguments in a different order.
asas
Example:
as foo; # same as: foo is; ...; end ...; end;
It works nicely in side-effect form:
fill foo # same as: content = fill foo as content
The new name for PROCESS
fill template.html # same as TT2: PROCESS template.html
NOTE: 'fill' isn't implemented yet. The parser will recognise the command, but it generates a "TODO: not working yet" message at runtime instead of actually filling the requested template.
with
creates a local variable scope for a block.
x=1; y=2; z=3; # create a block with local variables with x=10 y=20; "x is $x\n"; # x is 10 "y is $y\n"; # y is 20 z = 30; # local change only "z is $z\n"; # z is 30 end; # original values "x is $x\n"; # x is 1 "y is $y\n"; # y is 2 "z is $z\n"; # z is 3
with
also works in side-effect form:
fill template.html # same as TT2: INCLUDE template.html x=10 y=20 with x=10 y=20
NOTE: 'with' isn't implemented yet
just
is similar to with
but it create a local scope that only
contains the variables specified.
x=1; y=2; just x=5; "x is $x\n"; # x is 5 if y; # not reached - y is not defined in this inner scope end; z = 20; # local change only end; if z; # not reached - z is not defined in this outer scope end;
The parameters are optional. You can use the 'just' keyword by itself to create an empty variable scope:
just; ...no variables are defined here... end;
NOTE: 'just' isn't implemented yet
into
is the new name for WRAPPER.
block foo; "Hello $content"; end into foo; # Hello World "World"; end;
Note that into
is equivalent to the following:
fill foo with content as; "World"; end;
into
also works in side-effect notation
fill site/content info site/layout
NOTE: 'into' isn't implemented yet
if / elsif / else - conditional branches
if a; # simple test ...; end if a < 10 and b > 20; # arbitrarily complex expression ...; end if a; ...; elsif b; ...; else; ...; end;
'if' can also be used in side-effect notation
fill site/debug if user.developer;
NOTE: 'if' is implemented but 'elsif' and 'else' are TODO
Used to loop through lists
for a in b; # iterate over contents of list b, or just b if not a list ...; end for a in [b,c,d]; # inline list definition ...; end for a in 1..10; # ranges generate lists to iterate over ...; end; for a in 1 to 10; # 'to' is an alias for '..' ...; end for a in 10 to 20 by 2; # 'by' allows you to specify step ...; end
TT3 allows you to put 'elsif' and 'else' clauses onto 'for' blocks. These are evaluated if the list is empty. This example iterates over the items in a shopping basket. If there are no items in the basket we display one of two messages, depending on the user being logged in or not.
for item in basket.items; fill basket/item_summary elsif user.logged_id; "There are no items in your basket." else; "Please login to view the items in your basket." end
The above is short-hand for:
if basket.size; for item in basket.items; fill basket/item_summary; end; elsif user.logged_id; "There are no items in your basket." else; "Please login to view the items in your basket." end
There should be some way to create lockstep iterators. e.g. instead of writing this:
n = 1; for user in user; "$n: $user.name"; n++; end;
You should be able to create two iterators and run them in lockstep. I'm not sure about the best keyword to use, so I'm going with 'join' for now.
for user in user join n in 1 by 1 "$n: $user.name";
NOTE: 'for' is only partially implemented
'from' defines a data target for a block. Any variables accessed in the block will be matched from the target data.
stuff = { foo = 10 bar = 20 } from stuff; "foo is " foo; # 'foo' is actually 'stuff.foo' "bar is " bar; # 'bar' is actually 'stuff.bar' end;
I'm not decided 100% on what should happen if the data doesn't define a particular item. I'm currently thinking that it should fall back on looking for a regular variable.
stuff = { foo = 10 bar = 20 } baz = 10; from stuff; "foo is " foo; # 'foo' is actually 'stuff.foo' "bar is " bar; # 'bar' is actually 'stuff.bar' "baz is " baz; # 'baz' is just 'baz' end;
If you want to temporarily mask all the other variables you can use 'just'
just from stuff; ... end;
Which is just short-hand for:
just; # new, empty variable scope from stuff; # define data target ... end; end;
This also works quite nicely in side effect form:
fill user/info from user; fill user/info just from user;
NOTE: 'from' isn't implemented yet
Use this to encode a block of text. It's similar to TT2 FILTER, but it uses any of the codecs accessible by Badger::Codecs.
[% encode html %] ... [% end %] [% encode base64 %] ... [% end %]
This does the opposite of encode.
[% decode html %] ... [% end %] [% decode base64 %] ... [% end %]
This implements a low-precedence, block oriented form of the dot operator.
[% dot length -%] # same as 'Blah blah blah'.length Blah blah blah [%- end %]
In TT2 we had separate filters and text vmethods. In TT3 we just have text vmethods. The 'dot' keyword takes the place of the FILTER keyword, allowing you to define a block of text and then pipe it through any text virtual method.
You can pass arguments and chain multiple dotops together.
[% dot chunk(3).join(', ') -%] foobarbaz # outputs: foo, bar, baz [%- end %]
It also works in side effect form. Because it's low precedence (the same precedence as all command keywords), you can use it like this:
[% fill foo dot html %]
This is the same thing as:
[% (fill foo).html %]
But obviously not the same thing as:
[% fill foo.html %] # 'foo.html' is a filename
Looking at an earlier example:
if basket.size; for item in basket.items; fill basket/item_summary; end; elsif user.logged_id; "There are no items in your basket." else; "Please login to view the items in your basket." end
That can be written with braces if you prefer.
if basket.size { for item in basket.items { fill basket/item_summary; } } elsif user.logged_id { "There are no items in your basket." } else { "Please login to view the items in your basket." }
Or the minimalist approach:
if basket.size for item in basket.items fill basket/item_summary; elsif user.logged_id "There are no items in your basket." else "Please login to view the items in your basket."
Hmmm... wait, not that's not right. The 'elsif' will get attached to the inner 'for'. Ho hum. This is a good example of why a flexible language can be a bad thing. Flexibility == ambiguity. I can image things like that tripping people up often, myself included.
Possible solutions:
Don't add 'elsif' and 'else' to 'for'. People aren't expecting 'for' to have them, so the natural expectation is that 'elsif' binds to 'if'. That's why the above example looks right, but is wrong. If 'elsif' didn't bind to 'for' then the above would work because 'for' would reject the 'elsif' terminator, consider itself finished and pass control back to 'if' to pick up the 'elsif'
Don't allow follow-on keywords after single expressions.
if x y; # OK if x y; else z; # NOT OK
Don't allow follow-on keywords after single expressions unless they follow immediately, without any terminator.
if x y; else z; # NOT OK - semi-colon terminates single expression if x y else z; # MAYBE OK - lack of semi-colon indicates continuation.
Well that makes our example correct again:
if basket.size for item in basket.items fill basket/item_summary; # semi-colon ends 'for' expression elsif user.logged_id ... else ...
But I'm concerned that a single-semi-colon can change the meaning of an expression. It wouldn't be so bad if one form or the other was illegal because we would get an immediate parse error. But when both forms are legal syntax there is no warning.
if basket.size for item in basket.items fill basket/item_summary # no semi-colon elsif user.logged_id ... else ...
Perhaps I'm worrying too much. The above is the stripped down minimalist view that (common sense dictates) should only be used for simple cases. Given that the user has the choice of using explicit block form with semi-colons (if x; y; end) *OR* braces (if x { y }) then I probably don't need to feel too bad if they burn themselves using by deliberately using the ambiguous form.
Hmm...
Andy Wardley http://wardley.org/