Paul Seamons wrote:
I have released a new version of [...] Template::Alloy
> [...] allows you to use [lots of stuff]

Hi Paul,

First let me say what great work you've done on Template::Alloy. The significance of being able to plug in different template languages is certainly not lost on me. It's always been the #1 priority for TT3 - to clearly separate any particular template language from the general purpose template processing framework. This is not only an important milestone towards that, but also sets the standard for what I've got to catch up to!

Here's something of a brain dump on how it (works/will work) in TT3.

In TT3, all templates are instances of Template::Template (hereafer T::T). Or rather, they're instances of subclasses of T::T which specialise it for different languages:

  Template::Template::TT2
  Template::Template::TT3
  Template::Template::HT  (HTML Template)
  ...etc...

I hadn't considered Velocity or Text::Tmpl until now. But seeing as you've done all the hard work, I guess we can add them to the official list. I've also got PETAL pencilled in as something that would be nice to support.

The T:T base class provides some common methods returning information about the template:

  $t->uri()
  $t->name()
  $t->text()
  $t->meta()
  ...etc...

[aside]
The uri() method is worth mentioning because it ties in with another proposal you made regarding CACHE_STR_REF. If a template is loaded from a file then uri() will return the filename, or possible a more canonical URI like "file:/blah/blah" or even "file://localhost/blah/blah". If it's generated from a text string, then I *was* planning to use the Perl memory address of the text ref, although that could fail under certain conditions (e.g. shared memory cache between processes) and certainly isn't safe for serialisation. So the md5 sum sounds like a much better idea, either enabled by default or via a config option.
[/aside]

In TT3, you'll be able to use templates as stand-alone objects.  For example:

    my $f = Template->template( file => 'example.tt3' );
    my $t = Template->template( text => 'Hello [% thing %]' );

    print $t->process( thing => 'World' );
    print $t->process( thing => 'Badger' );

This is one important difference between TT2 and TT3. TT2 templates require a separate context object in order to do anything useful. TT3 templates will effectively *be* their own context, by virtue of the fact that Template::Template will be a subclass of Template::Context (or something like it - I'm still hacking on that bit ATM).

That means you can do things like this, in HTML-Template style:

   $t->set( thing => 'Badger' );
   $t->get('thing');

Each template will also have it's own methods for fetching and using things other than variables:

   $t->template('header');
   $t->plugin('table');
   $t->filter('html');

And so on.  (there's magic behind that which I'll skip over for now)

What happens in the background when you call process(), is that the template takes responsibility for parsing and compiling itself into a runtime form, and then runs itself. How it does all that it up to the template (and friends, like compilers, caches, stores, etc).

At the low-level end of things, this gives you a lot more flexibility. It means you can bypass the whole Template front-end mechanism (e.g. the service layer) if you want, and just get a template object that spits some text back out when you feed it variables.

   print $t->process( thing => 'Badger' );

It makes things like this much easier to implement:

   [% "Hello [% thing %]".eval %]
   [% "Hello [% thing %]".template %]
   [% "Hello [% thing %]".process(thing = 'Badger') %]

The other key benefit is that a template object can implement whatever kind of parsing *and* runtime strategy that it likes.

Standard TT3 template objects will compile themselves into something like this:

   sub {
       my ($self, $args) = @_;
       return "Hello " . $self->get('thing');
   }

It's almost exactly the same as in TT2, except we're using $self rather than $context, and there's a built-in method for get() rather than going through the stash.

The difference is that the template ($self) can implement it's own get() method, or do something else entirely to handle variables. It's no longer limited by what the $context provides. Nor is it even required to compile itself to Perl code. It can implement itself using an optree like TT1 or CET.

Having said that, a key part of TT is getting a bunch of templates to play nicely with each, rather than skulking around by themselves. When we INCLUDE a template, for example, we want it to get all the variables that we've currently got without us having to explicitly pass them over.

TT2 operates on the idea of a shared context. All variables are available everywhere unless you take measures to ensure that they localised. For example, by using INCLUDE rather than PROCESS. Internally this involves cloning the stash (which eats memory and time) and messing with the BLOCKS definition list (which is just plain ugly). And all it really achieves is to protect you from a SET in the called template or a BLOCK definition which would otherwise screw up your own variable/templates.

TT3 will operate on the idea of separate contexts that know how to play along nicely with each other (should they chose to do so). The outer template in the example above will do something like this in its generated code:

   $self->template_include('make_pretty');  # [% INCLUDE make_pretty %]

Which will do this:

   $template = $self->template('make_pretty');

And then this:

   $template->process({ }, context => $self);

The outer template passes a reference to itself as the 'context' for the inner template. The inner get() method can then call the $context->get() method to fetch any variables from the outer template and cache them locally. Although that first get() on a variable takes a shade longer, the local cache means you only take the hit once. And you win by only doing this for the variables you actually use, whereas the current stash cloning code copies all your variables, every time, regardless.

When you set() a variable, you set it in the local variable cache and don't affect the calling context. So you effectively get the protection that INCLUDE gives you for free. It's more like Perl in that sense: don't export anything by default! However, to implement the way PROCESS currently works, there will be some mechanism (got a couple of candidates but no firm code yet) to allow the template to set stuff in the context.

The other nice thing is that the same strategy works for all the resources: variables, templates, plugins, filters, and so on. There's no need for any more messing about with BLOCK definition lists and other special cases like that.

So to cut a long story short, getting all these different template languages to play nicely with each other is very much on the TT3 agenda. Although the underlying mechanism in TT3 will be slightly different from what you've got in Alloy (as I understand it), it's the same kind of abstraction operating at a slightly different level. And I think that this change in the way the context works will make the whole process easier.

Anyway it's certainly very inspiring for me to see a working implementation of these ideas in CET/Alloy. It's serving as both the carrot and the stick that's keeping me moving forwards on TT3.

I've got a shiny new version of the TT2 web site and an overhaul of all the documentation that I'm about to push out. The very next task on my TODO list is to set up a public subversion repository for the TT3 code so you can get your hands on what I've got. However, I'm interspersing that with Real Work[tm] for the next few months so I'll be dropping in and out a bit.

Cheers
A


_______________________________________________
templates mailing list
[email protected]
http://lists.template-toolkit.org/mailman/listinfo/templates

Reply via email to