Forum OpenACS Development: Re: new templating model: ideas, questions

Collapse
Posted by Andrew Piskorski on
Tom, could you explain please just what you plan to use this new templating engine of yours for? That's by far the part I understand least in this thread. I mean, are you just doing this for fun and to learn from, or do you actually plan to use it on a real live site somewhere? And if so, why, precisely? Where's the big win over existing systems?
Collapse
Posted by Tom Jackson on
Andrew, please refer to the "basic goals" in the initial message. I'll elaborate here:
  • Safe: I need a template language, tcl based, which is safe. By safe, I mean than a designer cannot create a template which does something bad to the system, like delete files, open sockets, access resources beyond what are needed. In practice, what can be done is determined by the controller script. This allows you to safely split design/display/view, from the model and controller. Designers or even users could provide templates without great risk to the system.

    Tcl has a few thing which make it impossible to make safe in a simple way. One problem is embeded commands, things like $a([exec rm -rf /]) look just fine to Tcl and the exec is executed first before Tcl realizes things are not good. The 'for' command is also unsafe. You can do 'for {script} {test} {script} {...}', and whatever is in the first (and third?) argument is executed. There is a way to make a safe interp using Safe Tcl, but this is unworkable in AOLserver, and cumbersome to setup. Also, Safe Tcl isn't about templating.

    This system allows a restricted set of what constitutes an argument. There cannot be unescaped '[' in an argument. Variable names must start with a letter and can only contain a limited set of chars, specifically excluding '['.

  • Portable: It would be nice if you could use the template system everwhere Tcl can be used: in OpenACS, AOLserver, Tcl packages, inside a database which supports Tcl, etc. This also implies that the compiler should work on buffers as well as files. I haven't made this change yet, but it should be easy.
  • Independent: the template compiler should not have to look outside of itself to figure out what to produce (or what is safe), templates compiled on one system will produce exactly the same result (tcl script) as on another system.
  • Modular: each template is a complete script. If-elseif-else and foreach statements have to be complete. Commands have to at least look like valid code. The compiler will check the grammer of the template and will produce an error if the template isn't valid. This is still similar to tcl: you can't verify the runtime validity, but you will not end up with strange errors at runtime due to bad syntax.
  • MVC Model: It is obvious that template systems are most useful for separating code (model/controller) from display (viewer). However, most systems allow arbitrary includes: this turns the template into the controller. In addition, if a system only works on files and cannot work in memory, you end up writing tcl code which handles the formatting/display functions. If templates can be handled as strings, and are known to be safe, you can use the MVC model everywhere, not just in the final stages of presentation. You can even use a template as a formatting tool: again safely.
Collapse
Posted by Tom Jackson on

I should add that another goal is to provide a system which takes advantage of the strengths of Tcl, in an attempt to avoid creating another 'language'. This compiler isn't a language, it simply enforces certain restrictions and transforms the input template into 'possibly valid' Tcl code. It also allows infinite extensibility without changes to the compiler by the use of a 'resource' tag. It is a simple hook that allows the developer to give access to additional resources to the designer. One resource 'type' is an include. It allows the designer to include the results of an external script or compiled template and allows the designer to pass variables along. The script/template is evaluated outside the context of the caller and the results are returned as text. A similar resource (probably called source) will allow an external script to be evaluted in the caller page so you can break templates into chunks for modularity and readability, and avoid passing variables. What resource types are used will not impact the compiler, the calling environment only needs to impliment:

  • resource::init
  • resource::add type name,
  • resource::exists type name and
  • resource::eval type name args

(I'm having trouble posting this in one chunk or forums doesn't like my text, I have a further example, if it doesn't show up, it is because forums didn't like it).

Collapse
Posted by Tom Jackson on

As an example of usefulness (although not beauty) take a look at this photo album. This wasn't an OpenACS site but since I had a simple photos package, written for OpenACS, I decided to adapt it to an AOLserver module. The original used ATS, and didn't use the 'grid' tag, so five images would just take up five columns and extend off the page. I didn't have a grid tag, but I did have an 'if' tag which takes standard tcl expressions, so it was easy to create a grid using a template fragment as simple as:

 [if {($photos(rownum) % $cols) == 0} ]
 </tr>
 <tr>
 [/if]

One nice feature of the ATS grid tag is that you can flip the orientation. In my system you would need to create a resource to do this, but it turns out to be very efficient. I created a proc which takes a list and creates another list, with a modified order, depending on the number of columns you want in the output. Since the foreach tag takes a list of arrays, the new list just accesses the arrays in a modified order, allowing you to create the flipped orientation.

I also found that the group tag is easy to replace. However, it is slightly ugly:

[set v ""]
[foreach row $rlist]
 <tr>
[if {"$v" ne $row(a)}]
  <th colspan="6" align="left">a = $row(a)</th>
 </tr>
 <tr>
  <td>&nbsp;</td>
[else]
  <td> </td>
[/if]
[set v $row(a)]
[/foreach]

You would have one if block for each group by.